--╔════════════════════════════╗═════════════════════════════════════════════════════════════════════════════════════════════════╗
--║  RAIL SPAWNING & DIRECTOR  ║ This is where rails are set up, connected, animated, destroyed, etc. The rail's foundation!	 ║
--╚════════════════════════════╝═════════════════════════════════════════════════════════════════════════════════════════════════║
--║												[DOCUMENTATION LINK]															 ║
--║			If you're here looking on how to USE rails, I've made much more comprehensible documentation here:					 ║
--║												  bit.ly/GSraildocs																 ║
--╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
local RAIL = GS_RAILS if RAIL.blockload return end
local Msa = {}
Msa["mapthing.stringargs"]   = true
Msa["mapthing_t.stringargs"] = true
Msa["line_t.stringargs"]	 = 2 --One of these is not like the other...

--The Rail Director tracks all rails so we may avoid having to iterate through too many mobjs or individual rail segments.
--It's more optimal to animate rails this way too, instead of adding MobjThinkers to all rails incase they wanna animate.
RAIL.DirectorThinker = function(s)
	if (s.GSTIME==leveltime) and (s.GSTIME!=nil) return end --
	s.fuse = 99
	if not (s.GS_SetupRailDirector)
		if ((RAIL.MapReady or 0) > 1)
			local DIRECTOR = GS_RAILS_DIRECTOR
			if (DIRECTOR!=s) and type(DIRECTOR)=="userdata" and userdataType(DIRECTOR)=="mobj_t"
			and DIRECTOR.valid and (DIRECTOR.type==MT_GSRAILDIRECTOR) and (DIRECTOR.GS_SetupRailDirector)
				s.fuse = min($, 1) P_RemoveMobj(s) return --Whao, do NOT spawn two!!!
			elseif (RAIL.SetupRailDirector(s)!=false)
				s.GS_SetupRailDirector = 1
			end
		else
			RAIL.MapReady = (RAIL.MapReady or 0)+1 
			if s.fuse s.fuse = $+1 end
		end
		s.GSTIME = leveltime return --Hol'up for now.
	end
	s.fuse = 99<<16 --Keep us alive!
	
	--Set up is done! Check colors?
	if (s.GS_SetupRailDirector < 250)
		s.GS_SetupRailDirector = $+1 --How long it's been setup. Let's stop at 250, 7-ish seconds into the level.
	/*	if (s.GS_SetupRailDirector%12==0) and s.GSrailtable 
			if not (#(s.GSrailtable)) --We've got no rails, so...skip through, I guess!
				s.GS_SetupRailDirector = $+10
			else
				for _,m in pairs(s.GSrailtable) --Undoing a superspecific color-bug on load, when rawget apparently won't work yet...
					if not (m and m.valid and not (m.GSraildye) and (m.type==MT_GSRAIL)) continue end --
					
					local COLOR = RAIL.GrabRailColor(m)
					if (COLOR)
						m.GSraildye,m.color = COLOR,COLOR
						local SIDE = m.GSrailsideL
						if SIDE and SIDE.valid
							SIDE.GSraildye,SIDE.color,SIDE.colorized = m.GSraildye,m.color,m.colorized
						end
						if m.GSsegments --Look into all rail segments now.
							for _,seg in pairs(m.GSsegments)
								if not (seg and seg.valid) continue end --
								seg.GSraildye,seg.color,seg.colorized = m.GSraildye,m.color,m.colorized
								SIDE = seg.GSrailsideL
								if SIDE and SIDE.valid
									SIDE.GSraildye,SIDE.color,SIDE.colorized = m.GSraildye,m.color,m.colorized
								end
							end
						end
					end
				end
			end
		end */
	end
	
	--Animate every rail in the stage that has animations attached. Also activates special rail effects.
	local TABLE = s.GSanimator
	if TABLE and (#TABLE) --Oh,we animating some stuff tonight? Cool, cool.
		local DO,FRAME,ADVANCE,ANIMSPEED = s.DO or 0, A, false, 0
		local SIDE,SEGS,RNG = nil
		if (s.GS_RNG!=nil)
			s.GS_RNG = P_RandomRange(-10000,10000) --Alternate RNG every frame once introduced!
			RNG = s.GS_RNG
		end
	
		for _,m in pairs(TABLE)
			if not (m and m.valid) or (m.GSdisabled or m.GSinvisiblerail) continue end --
			
			ANIMSPEED = max(1, m.GSinfo and m.GSinfo.animspeed or 0)
			ADVANCE = false
			
			if m.top_endframe or m.side_endframe
				if (ANIMSPEED<=1) or (leveltime%ANIMSPEED==0)
					ADVANCE = true --Let the others know they can advance a frame too.
					if m.top_endframe
						if ((m.frame & FF_FRAMEMASK) >= m.top_endframe)
							m.frame = ($ & ~FF_FRAMEMASK)|(m.top_startframe)
						else
							m.frame = $+1
						end
					end
					if m.side_endframe --Yes, the main rail object can have a side too!
						SIDE = m.GSrailsideL
						if SIDE and SIDE.valid
							FRAME = (SIDE.frame & FF_FRAMEMASK)
							if ((SIDE.frame & FF_FRAMEMASK) >= m.side_endframe)
								SIDE.frame = ($ & ~FF_FRAMEMASK)|(m.side_startframe)
							else
								SIDE.frame = $+1
							end
						end
					end
				end
			end
			if (ADVANCE) or m.GS_windrail
				if m.GS_windrail and m.GSnextrail and m.GSnextrail.valid
					if RNG==nil
						s.GS_RNG = P_RandomRange(-10000,10000)
						RNG = s.GS_RNG
					end					
					RAIL.SpawnRailWind(DO, RNG, m, m, m.GSrailsideL or SIDE, true)
					DO = $+1
				end
				if m.GSsegments --Look into the rail object's individual segments now.
					SEGS = m.GSsegments
					local COUNTME = 0
					local SEGNUM = #SEGS or 0
					for _,seg in pairs(SEGS)
						if not (seg and seg.valid) continue end
						if (ADVANCE) --Animate the segments?
							if (m.top_endframe!=nil)
								FRAME = (seg.frame&FF_FRAMEMASK)
								if (FRAME >= m.top_endframe)
									seg.frame = ($&~FF_FRAMEMASK)|(m.top_startframe)
								else
									seg.frame = $+1
								end
							end
							if (m.side_endframe!=nil)
								SIDE = seg.GSrailsideL
								if SIDE and SIDE.valid
									FRAME = (SIDE.frame&FF_FRAMEMASK)
									if (FRAME >= m.side_endframe)
										SIDE.frame = ($&~FF_FRAMEMASK)|(m.side_startframe)
									else
										SIDE.frame = $+1
									end
								end
							end
						end
						if m.GS_windrail and m.GSnextrail and m.GSnextrail.valid
							COUNTME = $+1
							if (SEGNUM < 4) or not (COUNTME > SEGNUM-3)
								RAIL.SpawnRailWind(DO, RNG or 0, m, seg, seg.GSrailsideL or SIDE) --Add wind effects to reach segment
							end
							DO = $+1
						end
					end
				end
			end
		end
		s.DO = DO
	end
	s.GSTIME = leveltime
end

--What to do? If VAR is "PostThink", the thinker can play.
--Otherwise, we can spawn the director, check if somebody else has it, or check in general?
RAIL.DirectorSpawnOrThink = function(DIRECTOR, VAR)
	if type(DIRECTOR)=="userdata"
		if userdataType(DIRECTOR)=="mobj_t" and DIRECTOR.valid
			if VAR=="PostThink"
				RAIL.DirectorThinker(DIRECTOR) return DIRECTOR, false --Perfect!
			else
		--		print("VAR:\x8d"+VAR)
		--		print("THINK-DIRECTOR ID:\x88"+DIRECTOR+"\x86 FOUND:"+FOUNDDIRECTOR+" NETJOIN:"+NETJOIN+" MAPREADY:"+RAIL.MapReady+" lvltime:"+leveltime)
			end
		else
	--		print("VAR(ALMOST!):\x8d"+VAR+"\x80 DIRECTOR:\x87"+DIRECTOR)
		end
		return DIRECTOR, true --	
	end
	--print("VAR:\x8d"+VAR)
	if VAR=="CheckOnly" return DIRECTOR, true end --You were only checking for validity?
	
	local FOUNDDIRECTOR, NETJOIN = false, nil --Let our quest begin!
	if netgame==true and (VAR!="spawnonly") and consoleplayer==displayplayer and consoleplayer and consoleplayer.valid
	and ((consoleplayer.jointime or 0) < 99) and not (consoleplayer.bot)
		NETJOIN = consoleplayer --We need to sync this somehow for players.
	end

	if (NETJOIN!=nil)
		for z in players.iterate
			if z and z.valid and (z.GS_RAILDIRECTOR)
			and type(z.GS_RAILDIRECTOR)=="userdata"  and userdataType(z.GS_RAILDIRECTOR)=="mobj_t"
			and z.GS_RAILDIRECTOR.valid and (z.GS_RAILDIRECTOR.type==MT_GSRAILDIRECTOR)
				GS_RAILS_DIRECTOR = z.GS_RAILDIRECTOR
				DIRECTOR = z.GS_RAILDIRECTOR --print("\x82"+"FOUND OLD DIRECTOR ON PLAYER:\x8a"+GS_RAILS_DIRECTOR)
				FOUNDDIRECTOR = 1 
				for zm in players.iterate if zm and zm.valid zm.GS_RAILDIRECTOR = DIRECTOR end end
				break --Our fellow players were so kind as to redirect us.
			end
		end
	end
	if (FOUNDDIRECTOR!=false)
		return DIRECTOR, false --
	else
		if (NETJOIN!=nil) and (RAIL.MapReady) and (VAR!="spawnonly") --Is there one anywhere in the stage...??
			for m in mobjs.iterate(mobjs)
				if m and type(m)=="userdata" and userdataType(m)=="mobj_t" and m.valid and (m.type==MT_GSRAILDIRECTOR)
					GS_RAILS_DIRECTOR = m 
			--		print("\x86"+"FOUND A DIRECTOR IN MAP:\x82 "+GS_RAILS_DIRECTOR)
					DIRECTOR = m
					DIRECTOR.GSTIME = leveltime
					FOUNDDIRECTOR = 2
					for z in players.iterate if z and z.valid z.GS_RAILDIRECTOR = DIRECTOR end end
					break --
				end
			end
		end
		if FOUNDDIRECTOR==false and (VAR!="nospawn") 
			if (netgame!=true) or consoleplayer and (consoleplayer==displayplayer) or displayplayer==nil --Make a new one.
				GS_RAILS_DIRECTOR = P_SpawnMobj(995, 995, 22999<<16, MT_GSRAILDIRECTOR)
				DIRECTOR = GS_RAILS_DIRECTOR
				DIRECTOR.GSTIME = leveltime 
				FOUNDDIRECTOR = 3
			--	print("\x81 MADE A NEW DIRECTOR:\x83 "+DIRECTOR)
				for z in players.iterate if z and z.valid z.GS_RAILDIRECTOR = DIRECTOR end end
			end
		end
	--	print("DIRECTOR ID:\x88"+DIRECTOR+"\x86 FOUND:"+FOUNDDIRECTOR+" NETJOIN:"+NETJOIN+" MAPREADY:"+RAIL.MapReady+" lvltime:"+leveltime)
	end return DIRECTOR,(FOUNDDIRECTOR==false) --
end

--Takes the flags out of a number, and returns them.
RAIL.GETFLAGS = function(NUM, STARTPOINT) if not (NUM) return 0 end
	local FLAGS, I = 0, STARTPOINT or FRACUNIT
	for i = 1,24
		if (NUM & I) FLAGS = $+I end
		I = $*2 if (I < 0) break end --
	end return FLAGS --
end

--Return rail's proper angle, rather than the angle accounting for curves.
RAIL.ANG = function(rail)
	if rail==nil RAIL.error("Warning! RAIL.ANG function: given rail is nil!") return 0 end
	if rail.GSmainrail rail = rail.GSmainrail end
	
	if rail.GSforceUDMFang!=nil and rail.GSVertRail
		return rail.GSforceUDMFang
	elseif rail.GSrailTrueAngle!=nil 
		return rail.GSrailTrueAngle --
	else
		return rail.angle or 0--
	end
end

/* --Experimental unused feature.
RAIL.Quake = function(INTENSITY, TIME, EPICENTER, RADIUS)
	INTENSITY = INTENSITY or 5<<16
	TIME = TIME or 2
	if type(EPICENTER)=="userdata" --Input an object, and it'll use the object's coordinates.
		EPICENTER = {EPICENTER.x, EPICENTER.y, EPICENTER.z}
	end
	if type(EPICENTER!="table") --If it's not a table, skip the epicenter entirely.
		P_StartQuake(INTENSITY, TIME)
	elseif displayplayer and displayplayer==consoleplayer and displayplayer.mo
		RADIUS = RADIUS or 128<<16
		local s = displayplayer.mo
		local X,Y,Z = EPICENTER
		local DIST = FixedHypot(FixedHypot(s.x-X, s.y-Y), s.z-Z)
		if (DIST > RADIUS) return end --Don't play a quake at all.
		if (DIST > RADIUS/2) --If we're barely within the radius, start reducing intensity a bit.
			INTENSITY = FixedDiv(FixedMul($, DIST), RADIUS*2)
		end
		P_StartQuake(INTENSITY, TIME, EPICENTER, RADIUS)
	end
end */

local function GIT(v) return _G[v] end
RAIL.TypeExists = function(v) return pcall(GIT, v) end

--Snags a rail's color.
RAIL.GrabRailColor = function(s)
	if not (s and (type(s)=="userdata") and s.valid) return 0 end --Not even valid..?

	if s.GSmainrail and s.GSmainrail.valid and (s.GSmainrail.spawnpoint) and (s.type!=MT_GSRAIL)
		s = s.GSmainrail --Use them instead.
	end
	local spn,SARG = s.spawnpoint, nil
	if spn and type(spn.stringargs)=="userdata" and Msa[userdataType(spn.stringargs)]==true and spn.stringargs[0]
		SARG = spn.stringargs
		local COLOR,NUM = SARG[0], nil --Custom rail color. Convenience feature for UDMF that doesn't require dye objects.
		if type(COLOR)=="string"
			NUM = R_GetColorByName(COLOR) --That didn't work. Try converting names?
			if not (NUM)
				NUM = R_GetSuperColorByName(COLOR) --SUPER name???	
				if not (NUM)
					NUM = rawget(_G, COLOR) --First, try converting SKINCOLOR_ constants. (Apparently _G needs a moment to load?)
					if not (NUM)
						NUM = tonumber(COLOR) --Did they input a number for some reason?
						if not (NUM)
							local EXISTS, VALUE = RAIL.TypeExists(COLOR)
							if EXISTS==true and type(VALUE)=="number"
								NUM = VALUE
							end
						end
					end
				end			
			end
		end
		if type(NUM)=="number" and (NUM) and not (NUM > #skincolors)
		/*	if s.GSrailnum==7151
				print("COLOR AS NUMBER:\x82"+NUM+" SARG:"+userdataType(SARG)+"\x80 - SARG color:\x88"+COLOR)
			end */
			return NUM --Final say!
		end
	end return 0 --print("\x85 No Late.\x80 SARG:\x82"+userdataType(SARG)+" SARG[0]:"+SARG[0], s.GSrailnum)
end

--A function to try kill formatting mistakes for mappers
local function TRUEorFALSE(m)
	if not (m)
		return false --
	elseif type(m)=="string"
		local TEXT = string.lower(m)
		if (string.find(TEXT, "true")) or (string.find(TEXT, "on")) or (string.find(TEXT, "yes"))
			return true --
		end
	elseif m==true or m==1
		return true --
	end return false --
end

--This is the main rail object that was actually placed in the map by a mapper. Here we set up its properties.
--These properties are passed onto the segments, sides, and possibly even the next rails, therefore this must be FIRST step!
--ActivateRail is called by the Rail Director, then chains into ConnectRails1>2>3>4, which make individual rail segments.
RAIL.ActivateRail = function(m)
	if not (m and type(m)=="userdata" and userdataType(m)=="mobj_t" and m.valid) return end --
	
	local spn = m.spawnpoint
	if not ((spn) and type(spn)=="userdata" and userdataType(spn)=="mapthing_t" and spn.valid) return end --	
	
	if m.GSrailsetup RAIL.error("Warning! Tried to activate rail["+m.GSrailnum+"] twice!") return false end
	
	if m.GSMapThinged=="TryAgain" --The fuck. MapThing. Do it!
		RAIL.MapThingSpawn(m, spn, true)
	/*	if m.GSMapThinged=="TryAgain"
			print("\x85"+"Failed TR:\x86 "+m.GSMapThinged) return "AbortMT"
		else
			print("\x83"+"YES THIS TIME! railnum: \x82"+m.GSrailnum)
		end */
	end
	local MAP = mapheaderinfo[gamemap]
	
	m.scale,m.spritexscale,m.spriteyscale = FRACUNIT,FRACUNIT,FRACUNIT --ALWAYS start with the same scale, then adjust as needed.
	m.GSXYZ = {X = m.x, Y = m.y, Z = m.z} --Original coordinates.
	m.momx,m.momy,m.momz,m.pmomz = 0,0,0,0,0
	m.state = S_GSRAIL --Mostly to ensure UZB didn't interfere somehow.
	m.sprite = SPR_GSRL
	m.frame = ($ & ~FF_FRAMEMASK)
	m.spritexoffset = m.GSspritewidth or 0
	
	m.GSrailsetup = true --this rail had been setup!

	m.flags = $|MF_SOLID & ~(MF_NOCLIP|MF_NOCLIPTHING|MF_NOBLOCKMAP|MF_NOTHINK)
	m.flags2 = $ & ~(MF2_DONTDRAW|MF2_OBJECTFLIP) --We can set the sprite to SPR_NULL for invisible grindable rails.
	m.eflags = $ & ~(MFE_VERTICALFLIP|MFE_APPLYPMOMZ|MFE_JUSTHITFLOOR|MFE_SPRUNG) --Never be flipped!
	
	m.GScantgrind = true --Rails cannot grind themselves, shockingly
	m.GSrailtophitbox = m.GSrailtophitbox or 0 --Vertical hitboxes are sometimes adjusted to feel more correct depending on slope angle.
	m.GSrailbottomhitbox = m.GSrailbottomhitbox or 0 
	m.GSrailTrueAngle = m.angle

	--Now check spawnpoint parameters!
	--#======================================Binary Map arguments====================================================#
	if (spn.pitch)
		m.GSpitch = spn.pitch --Temporary values for easier identification.
	end
	if (spn.roll)
		m.GSroll = spn.roll
	end	
	if MAP --Mapheader modifiers.
		if MAP.gsrailcam and type(MAP.gsrailcam)=="string"
			local CAM = string.lower(MAP.gsrailcam)
			if CAM=="force"
				m.GSforcecam = "force"
			elseif CAM=="always"
				m.GSforcecam = "always"
			elseif CAM=="off"
				m.GSforcecam = "off"
			end
		end
	end
	
	
	if (udmf)!=true --Binary map options! We can't modify it as much, unfortunately.
	
		local OPTIONS = spn.options
		if OPTIONS
			if (OPTIONS & MTF_EXTRA) -- Flag 1. Invisible rail. Useful for all kinds of stuff.
				m.sprite = SPR_NULL
				m.GSinvisiblerail = true
			end
			if (OPTIONS & MTF_OBJECTFLIP) -- Flag 2. Object Flip. Does nothing on rails, so I made it an SA2 jumpramp.
				m.GSjumprail = 1
			end		
			if (OPTIONS & MTF_OBJECTSPECIAL) --Flag 4. Hang style rail.
				m.GShangrail = true
				m.color = SKINCOLOR_LAVENDER
			end
			if (OPTIONS & MTF_AMBUSH) --Flag 8. Attach rings/bumpers/springs/boosters vertically alligned with the rail
				m.GSrailallignobjects = true
			end
		end
		if (spn.extrainfo) --Custom rails. Unfortunately binary can only support up to railtype 15.
			m.GStype = spn.extrainfo
			if m.GStype==15 --Always apply Frontiers leap property to Tropical Resort rails.
				if not (m.GSjumprail)
					m.GSjumprail = 2
				end
			elseif m.GStype==12 --Fences can become walljump rails when vertically stacked.
				if m.GSinvisiblerail and not m.GShangrail
					m.GScheckwalljump = true
				end
			elseif m.GStype==7 --Give Metal Harbor rails Mega Jump properties if the FLIP flag is applied.
				if m.GSjumprail
					m.GSjumprail = nil
				end
				m.GSjumpflags = 2097152
			elseif m.GStype==4 --Always apply a 78% speed modifier to City Escape rails
				m.GSrailspeed = 78
			elseif m.GStype==3 --City Escape handrails with poles have alot of special nonsense.
				if m.GSinvisiblerail==true --Apply loop properties.
					m.GStype = 42
					m.GSLoopType = 1 --Default loop.
					m.GSjumpflags = 393216 --Disable sidehop & C3.
					if m.GShangrail==true
						m.GSloopflags = 2228224
						m.GSjumprail = nil
					end
				elseif m.GShangrail==true --Make an '06 jumprope if it's not invisible.
					if not m.GSjumprail
						m.GSjumprail = 2
					end
					m.GStype = 18
					m.GSjumpflags = 2490368
					m.GSgrindflags = 1835008					
				end
				m.GShangrail = nil --Never ACTUALLY use hangrail properties.
				spn.options = $ & ~MTF_OBJECTSPECIAL
			elseif m.GStype==2
				if MAP and type(MAP.gsrailtypetwo)=="number" --Make all railtype 2's into a different one?
					m.GStype = MAP.gsrailtypetwo
				end				
			elseif m.GStype==1
				if MAP and type(MAP.gsrailtypeone)=="number" --Make all railtype 1's into a different one?
					m.GStype = MAP.gsrailtypeone
				end
			elseif not (m.GStype)
				if MAP and type(MAP.gsrailtypezero)=="number" --Make all railtype 1's into a different one?
					m.GStype = MAP.gsrailtypezero
				end				
			end
		end
		
	--#========================================UDMF Map arguments========================================================#	

	elseif type(spn.args)=="userdata" and (userdataType(spn.args)=="mapthing.args" or userdataType(spn.args)=="mapthing_t.args")
	
		if spn.scale and (spn.scale!=FRACUNIT) --Scaling rails isn't quite so simple...
			m.GSxMTscale = spn.scale --m.GSyMTscale = spn.scale
		else
			if spn.spritexscale and (spn.spritexscale!=FRACUNIT)
				m.GSxMTscale = spn.spritexscale
			elseif spn.spriteyscale and (spn.spriteyscale!=FRACUNIT)
				m.GSyMTscale = spn.spriteyscale
			end
		end
		local COLOR
		if spn.stringargs and Msa[userdataType(spn.stringargs)]==true
			COLOR = RAIL.GrabRailColor(m) if (COLOR) and (COLOR!=0) m.GSraildye,m.color = COLOR,COLOR end
			local WILDCARD = spn.stringargs[1]
			if type(WILDCARD)=="string"
				if string.find(WILDCARD, "SPR2_") or string.find(WILDCARD, "spr2_") --They've input an animation!
					local SUB = string.find(WILDCARD, "-") if SUB==nil SUB=string.len(WILDCARD)+1 end
					WILDCARD = string.upper($)
					
					local SPRITE2 = string.sub(WILDCARD, 6, SUB) 
					if SPRITE2=="RUN" or SPRITE2=="FLY" or SPRITE2=="FLT"
						SPRITE2 = $+"_" --Goddamnit, game.
					elseif SPRITE2=="RUN-" or SPRITE2=="FLY-" or SPRITE2=="FLT-"	
						SPRITE2 = string.gsub($, "-", "_")
					else
						SPRITE2 = string.sub(WILDCARD, 6, SUB-1)
					end
					m.GSforcerailanim = SPRITE2
					local SPRITE3 = string.sub(WILDCARD, SUB+6) --Do we want a backup animation? ...probably.
					if string.find(WILDCARD, "SPR2_", SUB-1)
						if (string.len(SPRITE3)>=3)
							if SPRITE3=="RUN" or SPRITE3=="FLY" or SPRITE3=="FLT"
								SPRITE3 = $+"_" --Goddamnit, game.
							end
							m.GSforcerailanim2 = SPRITE3 --Backup.
						end
					end
				else
					WILDCARD = string.lower($)
					if string.find(WILDCARD, "onlyskin") --Only this character can grind!
						local SKIN = string.sub(WILDCARD, 9)
						m.GSonlyskin = SKIN
					elseif string.find(WILDCARD, "notskin") --This character CANNOT grind
						local SKIN = string.sub(WILDCARD, 8)
						m.GSnotskin = SKIN
					elseif string.find(WILDCARD, "norolling")  --You are not allowed to roll!
						m.GSnorolling = true					
					end
				end
			end
		end
	
		local ARG = spn.args		
		if ARG[0] --Turns rails invisible if 1.
			if ARG[0]==2
				m.GSdisableonwavethree = true
			else
				m.sprite = SPR_NULL 
				m.GSinvisiblerail = (ARG[0]==1) and true or ARG[0]
			end
		end
		if ARG[1] and type(ARG[1])=="number" --Turns rail into a hangrail.
			m.GStouchflags = RAIL.GETFLAGS(ARG[1], FRACUNIT)
			local VAR = ARG[1]
			if m.GStouchflags
				VAR = $ & ~m.GStouchflags
			end
			if VAR==1
				m.GShangrail = (ARG[1]==1) and true or ARG[1]
				m.color = SKINCOLOR_LAVENDER
			end	
		end
		if ARG[2] --Automatically allign items with the rail?
			m.GSrailallignobjects = (ARG[2]==1) and true or ARG[2]
		end
		
		--ARG[3] is the Rail Number argument. It's called instantly in the MapThingSpawn hook.
		
		if ARG[4] and type(ARG[4])=="number" --Jump Settings! Such as an SA2 JumpRamp, or Frontiers style leaps!
			m.GSjumpflags = RAIL.GETFLAGS(ARG[4], FRACUNIT)
			if m.GSjumpflags
				m.GSjumprail = ARG[4]&~m.GSjumpflags
			else
				m.GSjumprail = ARG[4]
			end
			if m.GSjumprail==4
				m.GSjumprail = -1
			end
		end
		if ARG[5] and type(ARG[5])=="number" --Custom Rail Types!
			m.GStype = ARG[5]
		end
		
		if ARG[6] and type(ARG[6])=="number" --Forcibly Connect to Pitch.
			m.GSConnectToPitch = ARG[6]
		end
		if ARG[7] and type(ARG[7])=="number" --Grind Settings
			m.GSgrindflags = RAIL.GETFLAGS(ARG[7], FRACUNIT)
			local VAR = ARG[7]
			if m.GSgrindflags
				VAR = $ & ~m.GSgrindflags
			end
			if VAR==1
				m.GSfaceforward = 1 --Force forwards.
			elseif VAR==2
				m.GSfaceforward = -1 --Force backwards
			end
		end
		if ARG[8] and type(ARG[8])=="number" --Rail Speed Modifiers. The conveyors are kind of like Sonic Lost World rails!
			local SPEEDFLAGS = (RAIL.GETFLAGS(ARG[8], 16384)) --Speed Modifier flags.
			local VAR = ARG[8] --Actual input value.
			if SPEEDFLAGS
				VAR = (ARG[8]&~SPEEDFLAGS)
				if VAR
					if (SPEEDFLAGS & 16384) --Regular Conveyor.
						m.GSconveyor = VAR
					elseif (SPEEDFLAGS & 32768) --Reverse conveyor.
						m.GSconveyor = -VAR
					end
					if (SPEEDFLAGS  & 65536) --Railspeed %
						m.GSrailspeed = abs(VAR)
					end
					if (SPEEDFLAGS & 131072) --Start Cap
						m.GSrailstartcap = abs(VAR)
					end
				end
			elseif VAR --If no flag is picked, go with a conveyor.
				m.GSconveyor = min(500, max(-500, VAR))
			end
		end
		if ARG[9] --Loop Types/WallJumps/Cameras/Misc section.
			m.GSloopflags = RAIL.GETFLAGS(ARG[9], FRACUNIT)
			if m.GSloopflags
				m.GSLoopType = ARG[9]&~m.GSloopflags
			else
				m.GSLoopType = ARG[9]
			end
			if (m.GSloopflags & 1048576) and m.GSinvisiblerail==true
				m.GStype = 42 --For good measure.
			end
		end
		if m.GStype==2
			if MAP and type(MAP.gsrailtypetwo)=="number" --Make all railtype 2's into a different one?
				m.GStype = MAP.gsrailtypetwo
			end				
		elseif m.GStype==1
			if MAP and type(MAP.gsrailtypeone)=="number" --Make all railtype 1's into a different one?
				m.GStype = MAP.gsrailtypeone
			end
		elseif not (m.GStype)
			if MAP and type(MAP.gsrailtypezero)=="number" --Make all railtype 1's into a different one?
				m.GStype = MAP.gsrailtypezero
			end
		end
	end
	if (m.GStype) and ((RAIL.LearnRailType(m))!=0)
		local TYPE = GS_RAILS_TYPES[m.GStype]
		if not (m.GSraildye) and (TYPE.defaultcolor!=nil)
			m.color = TYPE.defaultcolor
		elseif (m.GSraildye)
			m.color = m.GSraildye
		end
		
		if TYPE.noSIDE 
			m.GSrenderstyle = "nosides"			
		elseif TYPE.noTOP
			m.GSrenderstyle = "notop" --Only 1 can be active at a time.
		end
		
		--Now make an INFO table. One I'm super inconsistent about actually using. Whoops.
		if type(m.GSinfo)!="table" m.GSinfo = {} end
		local INFO = m.GSinfo
		
		if TYPE.renderflags
			m.renderflags = $|(TYPE.renderflags)
			INFO.renderflags = TYPE.renderflags
		end
		if TYPE.blendmode
			m.blendmode = TYPE.blendmode
			INFO.blendmode = TYPE.blendmode
		end		
		if TYPE.hangZoffset
			INFO.hangZoffset = TYPE.hangZoffset or 0
		end
		if type(TYPE.TOPsprite)=="number" and (TYPE.TOPsprite) --Change sprite?
			if not m.GSinvisiblerail
				m.sprite = TYPE.TOPsprite
				m.frame = TYPE.TOPstartframe or A
			end
			INFO.TOPsprite = TYPE.TOPsprite
			INFO.TOPstartframe = TYPE.TOPstartframe or A			
			INFO.animspeed = max(TYPE.animspeed or 0, 1)
			if type(TYPE.TOPendframe)=="number"
				m.top_startframe = TYPE.TOPstartframe or A 
				m.top_endframe = max(m.top_startframe,TYPE.TOPendframe)
			end
		end
		if type(TYPE.SIDEsprite)=="number"
			INFO.SIDEsprite = TYPE.SIDEsprite
			INFO.SIDEstartframe = TYPE.SIDEstartframe or A
			INFO.animspeed = max(TYPE.animspeed or 0, 1)
			
			if type(TYPE.SIDEendframe)=="number"
				m.side_startframe = TYPE.SIDEstartframe or A
				m.side_endframe = max(m.side_startframe, TYPE.SIDEendframe)
			end
		end
		if TYPE.alwayscolorize
			if TYPE.alwayscolorize==2 --If it's 2, ALWAYS colorize no matter what.
				m.colorized = true
			elseif (m.color) and (m.color!=TYPE.defaultcolor)
				m.colorized = true --If it's anything else, only colorize if they're NOT the default color.
			end
		end
		if TYPE.animspeed and type((TYPE.animspeed))=="number"
			INFO.animspeed = abs(TYPE.animspeed)
		end		
		if (TYPE.translucent) and type((TYPE.translucent))=="number"
			INFO.translucent = TYPE.translucent
			m.frame = ($ & ~FF_TRANSMASK)|(TYPE.translucent)
		end
		if (TYPE.Xoffset) and type(TYPE.Xoffset)=="number"
			m.GSspritewidth = $+((TYPE.Xoffset)<<16)
			m.spritexoffset = m.GSspritewidth
			INFO.Xoffset = TYPE.Xoffset
		end
		if (TYPE.windrail) 
			if type(TYPE.windrail)=="number"
				m.GS_windrail = TYPE.windrail
			else
				m.GS_windrail = SKINCOLOR_GREY
			end
		end
		if TYPE.spawnleaves
			if type(TYPE.spawnleaves)=="number"
				INFO.spawnleaves = TYPE.spawnleaves
			else
				INFO.spawnleaves = SKINCOLOR_FOREST
			end
		end
	end
	if MAP --Add properties that can't get overwritten.
		if TRUEorFALSE(MAP.gsrailautobalance)==true
			if not (m.GSgrindflags & 262144) --Add autobalance flag.
				m.GSgrindflags = $|262144
			end
		end
		if TRUEorFALSE(MAP.gsrailnopowers)==true
			if (m.GSgrindflags & 65536) --Add or remove TwistDrive+Focus Grind flags.
				m.GSgrindflags = $ & ~65536
			else
				m.GSgrindflags = $|65536
			end
		end
		if TRUEorFALSE(MAP.gsrailnodislodge)==true
			if (m.GSjumpflags & 262144) 
				m.GSjumpflags = $ & ~262144 --Remove no-dislodge flag.
			else
				m.GSjumpflags = $|262144 --Add no-dislodge flag.
			end			
		end
		if TRUEorFALSE(MAP.gsrailfrontiers)==true and not (m.GSjumprail)
			m.GSjumprail = 2 --All rails are frontiers rails
		end	
		if MAP.gsrailspeed and not (m.GSrailspeed) --grind speed % modifier
			local MAXSPEED = tonumber(MAP.gsrailspeed)
			if ((MAXSPEED or 0) > 0) and (MAXSPEED < 100)
				m.GSrailspeed = MAXSPEED
			end		
		end
		if MAP.gsrailmaxspeed --An absolute cap for how fast you can grind.
			local MAXSPEED = tonumber(MAP.gsrailmaxspeed)
			if ((MAXSPEED or 0) > 0)
				m.GSrailmaxspeed = MAXSPEED
			end
		end 
		if not (m.GSgrindflags & (8388608|4194304))
			if MAP.gsrailforcehangdrop
				m.GSgrindflags = $|8388608
			elseif MAP.gsraildisablehangdrop
				m.GSgrindflags = $|4194304
			end
		end		
	end	
	local FLOORZ = P_FloorzAtPos(m.x, m.y, m.z, 0)
	if m.z==FLOORZ or m.z==m.floorz
		m.z = $+2 --Try not to be technically touching the floor.
	elseif m.z==m.ceilingz
		m.z = $-2
	end 
end

--Connect the rails, and do optional special effects. Angle 0 to 359 is sequence 1. Angle 360 to 719 is sequence 2, etc. 
--t is the director. 
--s is the rail we are connecting to the next one.
--PITCH is the pitch number we're trying to connect to if ConnectToPitch was chosen in UDMF.
RAIL.ConnectRails1 = function(s, t, PITCH)
	if (s.GSnextrail and s.GSnextrail.valid and s.GSprevrail and s.GSprevrail.valid) return end --Already fully connected. Skip!
	
	if (PITCH!=nil) --Happens AFTER initial setup, so GSpitch is already set.
		for _,m in pairs(t.GSrailtable)
			if (s!=m) and m.valid and (m.GSpitch==PITCH) and m.GSrailnum!=nil
				s.GSnextrail = m
				if not (m.GSprevrail and m.GSprevrail.valid) m.GSprevrail = s end --Connect to this if we never get another prevrail to prioritize.
				RAIL.ConnectRails2(s, s.GSnextrail)	return true --
			end
		end	return false --
	elseif (s.GSConnectToPitch) --You're called up later.
		return "wait" --
	end

	local NUM = (s.GSrailnum or 0)+1 --Convenience.
	for _,m in pairs(t.GSrailtable)
		if m and m.valid and m.GSrailnum==NUM --The closest rail with the next number. Takes priority.
		and not (m.GSprevrail and m.GSprevrail.valid) and not (s.GSnextrail==m or s.GSprevrail==m or m==s)
			s.GSnextrail = m
			m.GSprevrail = s
			RAIL.ConnectRails2(s, s.GSnextrail) return true --We found the best suitable next rail, connect it!	
		end
	end
end

--s is the current rail, m is the next rail. Make sure BOTH are valid.
--ActivateRail determines rail connections, and activates rail if everything is valid. That leads to ConnectRails 1 > 2 > 3 > 4
--ConnectRails2 sets up the rail lenght/angle/slope properties, and decides where to put down the segments.
--ConnectRails3 sets up the connecting segments and hitboxes. Aesthetically those segments use floorsprites.
--ConnectRails4 sets up papersprites underneath the segments. They're purely aesthetic.
RAIL.ConnectRails2 = function(s, m)	
	local X,Y,Z = s.x,s.y,s.z
	local MX,MY,MZ = m.x, m.y, m.z --Use the ORIGINAL coordinates for connections to prevent shenanigans
	if s.GSXYZ
		X,Y,Z = s.GSXYZ.X, s.GSXYZ.Y, s.GSXYZ.Z
	end
	if m.GSXYZ
		MX,MY,MZ = m.GSXYZ.X, m.GSXYZ.Y, m.GSXYZ.Z
	end

	s.GSrailXYlength = FixedHypot(X-MX, Y-MY) --XY distance only.
	s.GSrailZdist = Z-MZ --Z distance only.
	s.GSraillength = s.GSrailXYlength+abs(s.GSrailZdist) --True distance! Determine rail's distance to next rail.
	
	if not (s.GSraillength)
	or MZ==Z and MX==X and MY==Y
		RAIL.error("\x85"+"ERROR:\x80 rail[\x82"+s.GSrailnum+"\x85] and rail[\x82"+m.GSrailnum+
		"\x85] are sharing the exact same coordinates! \n"+"Spawned\x81 debug TOAD\x85 on location.")
		RAIL.SpawnDebug(s) P_RemoveMobj(s) P_RemoveMobj(m) return false --
	end

	local ZDELTA,TOPADD,BOTTOMADD = 0,0,0
	local UDMFANGLE = nil
	local RAILANG = R_PointToAngle2(X, Y, MX, MY)+ANGLE_180
	s.GSrailTrueAngle = RAILANG --Face the next rail
	s.angle = RAILANG
	
	local STARTZ,DESTZ,ZLIMIT = RAIL.VertRail(s, "check", m)
	if (ZLIMIT!=nil) or not (s.GSrailXYlength) --Purely vertical rail, then?
		if not (s.GSrailXYlength) 
			if s.GSUDMFang!=nil
				UDMFANGLE = s.GSUDMFang+ANGLE_180 --Specific case if UDMF's Rail Number field is used.
				s.GSforceUDMFang,s.GSrailTrueAngle,s.angle,RAILANG = UDMFANGLE,UDMFANGLE,UDMFANGLE,UDMFANGLE 
				s.GSrailXYlength = 0
			else
				RAIL.error("\x85"+"ERROR:\x80 Vertical rails can't share the exact same X & Y coordinates!\n RailNum was:\x82"
				+(s.GSrailnum or "unknown")+"\n"+"\x80"+"Move one of the stacked vertical rails at least 1 pixel!"+
				"\n"+"Spawned\x81 debug TOAD\x80 on location.")
				RAIL.SpawnDebug(s)
				P_RemoveMobj(s) P_RemoveMobj(m) return false --
			end
		end
		if s.floorspriteslope
			P_RemoveFloorSpriteSlope(s) --Should never be necessary, but just incase.
		end
		if s.GSrailZdist or (ZLIMIT!=nil)
			ZDELTA = ANGLE_90 	
			if (MZ > Z)
				TOPADD = $+max(0, ZLIMIT-s.height)
			else
				BOTTOMADD = $-ZLIMIT
			end
			s.GSrailXYlength = max(1<<16, s.GSrailXYlength or 0)
			s.GSVertRail = (MZ < Z) and -1 or 1 --Flag it as a downwards vertical rail or upwards?
			if UDMFANGLE!=nil
				s.GSVertRailDOWNANG = (UDMFANGLE+ANGLE_180)+((MZ > Z) and ANGLE_180 or 0)
			else
				s.GSVertRailDOWNANG = R_PointToAngle2(X, Y, MX, MY)+((MZ > Z) and ANGLE_180 or 0)
			end
			s.GSVertRailTopZ = MZ if (MZ < Z) s.GSVertRailTopZ = Z end
			s.GSVertRailBotZ = Z if (MZ < Z) s.GSVertRailBotZ = MZ end
			if s.GScheckwalljump==true
				s.GSLoopType = 4 --Become a walljump rail for binary.
				s.GSjumpflags = 393216
				s.GSgrindflags = 262144
				m.GSLoopType = s.GSLoopType
				m.GSjumpflags = s.GSjumpflags
				m.GSgrindflags = s.GSgrindflags 			
			end
		end	
	else
	
		s.frame = $ & ~FF_PAPERSPRITE
		s.flags2 = $|MF2_SPLAT
		s.renderflags = $|RF_FLOORSPRITE|RF_NOSPLATBILLBOARD|RF_ABSOLUTEOFFSETS	& ~RF_PAPERSPRITE
			
		if s.GSrailZdist --If there's Z distance, slope the rail!
			--ZDELTA = FixedAngle(FixedDiv(FixedDiv(FixedMul(s.GSrailZdist, FRACUNIT), s.GSrailXYlength), 1200)) --WTF
			--local Hdist = R_PointToDist2(X, Y, MX, MY)
			ZDELTA = R_PointToAngle2(0, 0, s.GSrailXYlength or 0, s.GSrailZdist)
			
			if ( abs(ZDELTA) >=  ANG1/2) //((s.GSrailXYlength>64<<16) and ANG1 or ANG1/9) + (ANG1/6) )
				local zdelt = abs(ZDELTA/ANG1) 
--				RAIL.print("Slope angle for rail "+s.GSrailnum+" to "+m.GSrailnum+": "+(AngleFixed(ZDELTA)/FRACUNIT))
				s.renderflags = $|RF_SLOPESPLAT
				P_CreateFloorSpriteSlope(s)
				s.floorspriteslope.o = {x = X, y = Y, z = Z}
				s.floorspriteslope.xydirection = RAILANG --SRB2 generates the slope backwards? Uhh, did I reverse the sprite? Oh well.
				s.floorspriteslope.zangle = ZDELTA --Set the rail's slope angle
			else		
				ZDELTA = 0
				if s.floorspriteslope
					P_RemoveFloorSpriteSlope(s) --Should never be necessary, but just incase.
				end
			end
			
			--Now modify the rail's vertical hitbox to allign better with the slope.
			--This method only works because we split them into segments no longer than 120 FRACUNITs.
			local ADDHITBOX = FixedInt(ZDELTA)*(FRACUNIT/-100) --If it works, it works!
			if (ADDHITBOX >= 0)
				if not s.GShangrail --Don't add a top hitbox if it's a hangrail! (Flipped rails do not exist, only flipped grind behaviour!)
					TOPADD = TOPADD and $+ADDHITBOX or ADDHITBOX
				end
			else
				BOTTOMADD = s.GSrailbottomhitbox and $+ADDHITBOX or ADDHITBOX
			end
			if (abs(s.GSrailZdist or 0) > (s.GSrailXYlength*3/2)+(31<<16)) --For vertical rails, massively stretch vertical hitboxes.
				if (m.z < s.z)
					BOTTOMADD = $-max(4<<16, abs(s.GSrailZdist)-s.height)
				else
					TOPADD = $+max(4<<16, (abs(s.GSrailZdist)-s.height))
				end
			end
		elseif s.floorspriteslope
			P_RemoveFloorSpriteSlope(s)
		end
	end
	s.GSrailTrueAngle,s.angle = RAILANG, RAILANG --Face the next rail! RAIL.ANG will try prioritizing GSrailTrueAngle
	s.GSrailtophitbox = TOPADD --Values to add and subtract for vertical hitbox.
	s.GSrailbottomhitbox = BOTTOMADD
	s.GSrailZDelta = ZDELTA --Angle value for the rail's "slope" if one exists. Might remain 0.
	s.radius = 64<<16 --Rail hitbox. Scale is irrelevant.
	if type(s.GSsegments)!="table"
		s.GSsegments = {} --Save all segments so we may mess with them later using Lua and the Rail Director.
	end
	local SEGS1,SEGSnum
	local DIVNO = 11520 --Bigger ends us with a shorter rail. Smaller gives a longer rail.

	--Connect them all with Rail Segments!	
	if ZLIMIT==nil
		SEGS1 = FixedDiv(FixedMul(s.GSrailXYlength, 100), DIVNO) --SEGS1  =R_PointToDist2(0, 0, s.GSrailXYlength, s.GSrailZdist)/120
		SEGS1 = max($,1) --At least be 1!
		SEGSnum = min(FixedInt(SEGS1)-1, 544) --No point adding more than 544 segments, that'd exceed the map limit.
	else
		SEGS1 = max(1, ZLIMIT/120) --Top sprites only, 120 pixels, soooo exactly this.
		SEGSnum = max(SEGS1/FRACUNIT, 1)
	end

	--Okay, so SRB2 just stops rendering sprites behind you if stretched out, so spriteyscale it out of the question.
	--s.spriteyscale = SEGS -- s.GSrailXYlength/120, basically. 120 is the rail's length in pixels.
	if (SEGSnum > 1) //or (SEGS1 > 75000) --If there's more than 1 segment needed, make a table.
		local lastseg,seg
		for i = 1,SEGSnum
			if (ZLIMIT!=nil) --Slightly altered version...
				seg = RAIL.ConnectRailsV3(s, i, lastseg)
			else
				seg = RAIL.ConnectRails3(s, i, lastseg) --Should I use SEGS or SEGS1?
			end
			if seg and seg.valid
				lastseg = seg
				s.GSsegments[i] = seg
			--	table.insert(s.GSsegments, i, seg) --The first segment is the main rail, so only add the next segments to this table!
				if i==SEGSnum and ZLIMIT==nil --The final segment!
					seg.spriteyscale = FixedDiv(FixedMul(FixedHypot(seg.x-MX, seg.y-MY), 100), DIVNO)
					seg.spriteyscale = max(abs($),1)
					seg.color,seg.colorized = s.color, s.colorized
					if seg.GSrailsideL and seg.GSrailsideL.valid
						RAIL.ScaleRailRollAngle(s, seg, seg.GSrailsideL)
					end
				end	
			end
		end 
	else --If it's a VERY short rail with only 1 segment, then use spriteyscale to shorten it appropriately. 
		if (ZLIMIT!=nil) --Vertical rail?
			s.spriteyscale = ZLIMIT/120 --Simple enough...
		else
			s.spriteyscale = SEGS1	--Non vertical rail stretch/squash.
		end
	end	
	--Now attach the sides, assuming we're not using a vertical rail.
	if ZLIMIT==nil --We're doing it here so the scaling was already done, btw.
		s.GSrailsideL = RAIL.ScaleRailRollAngle(s, seg, RAIL.ConnectRails4(s, s, -1))
		
		if not (SEGSnum > 1) and (s.GSrailXYlength >= 280<<16) and (s.spriteyscale > 152000) --Is it stretched out? 
		and not (s.GSloopflags & 2097152) and not (s.flags & MF_NOCLIPTHING)
			local STRETCH = RAIL.ConnectRails3(s, 1, nil, "STRETCH") --Spawn an invisible hitbox segment.
			if STRETCH and STRETCH.valid
				STRETCH.state = S_INVISIBLE
				STRETCH.flags = $|MF_NOSECTOR
				STRETCH.flags2 = $|MF2_DONTDRAW
				STRETCH.sprite = SPR_NULL
				STRETCH.GSstretchhitbox = STRETCH --You are the stretchbox!
				STRETCH.GSmainrail = s
				
				P_SetOrigin(STRETCH, 
				s.x-FixedMul(s.GSrailXYlength/2, cos(RAILANG)), 
				s.y-FixedMul(s.GSrailXYlength/2, sin(RAILANG)), s.z+(s.GSrailZdist/2) )
				s.GSstretchhitbox = STRETCH
			end
		end		
	end
	return true --Done!
end

--How big is the difference from one angle to another? This function returns the answer!
RAIL.AngleDifference = function(ANG_1, ANG_2, Rformat, maxANG, divide)
	local diff = (ANG_1-ANG_2)
	if not diff return 0,1 end --The result's 0. ...that was easy.	
	
	diff = (diff > ANGLE_180) and InvAngle(InvAngle(diff)/((divide) or 1)) or diff/((divide) or 1)	
	
	--This function NEVER gives negative results. 
	--ANGLE_270 or -ANGLE_90 for example would return as ANGLE_90, with PLUS simply becoming -1.
	local PLUS = (diff < 0) and -1 or 1
	if diff < 0 diff = InvAngle(diff) end
	
	if maxANG --Return a maximum value?
		diff = min(maxANG, diff)
	end
	if Rformat != 2
		if Rformat == 1 --Turns the result into FRACUNITs. From 0 to 180*FRACUNIT
			diff = AngleFixed(diff)
		elseif Rformat == 0 --Turns result into regular numbers. From 0 to 180.
			diff = (diff >= ANG1) and diff/ANG1 or 0
		end
	end
	return diff, PLUS --
end

--Next, add looping segments that connect the rails! These act as the main visuals and also the hitboxes.
--ALL values must be entered for this to work!
RAIL.ConnectRails3 = function(rail, segnum, lastseg, STRETCHHB)
	local RAILANG = RAIL.ANG(rail)
	--Spawn! One sprite is exactly 120 pixels, AKA 120 FRACUNITs in SRB2 dimensions.
	local m = P_SpawnMobjFromMobj(lastseg or rail, FixedMul(-120<<16, cos(RAILANG)), FixedMul(-120<<16, sin(RAILANG)), 0, MT_GSRAILSEG)
	if type(m)=="userdata" and userdataType(m)=="mobj_t" and m.valid
	/*	if lastseg and lastseg.valid
			if lastseg.GSrailsideL and lastseg.GSrailsideL.valid
				P_RemoveMobj(lastseg.GSrailsideL)
			end
		elseif rail.GSrailsideL and rail.GSrailsideL.valid
			P_RemoveMobj(rail.GSrailsideL)
		end	*/
		m.state = S_GSRAIL
		m.GSmainrail = rail
		m.sprite = rail.sprite
		m.frame = rail.frame
		m.GSrailTrueAngle = rail.GSrailTrueAngle
		m.angle = RAILANG
		m.GSsegnum = segnum
		m.GSrailnum = rail.GSrailnum
		
		m.flags = ($|MF_SOLID|MF_SCENERY) & ~MF_NOCLIPTHING
		m.flags,m.flags2 = rail.flags, rail.flags2 --We can always set the sprite to SPR_NULL for invisible grindable rails.
		m.renderflags = rail.renderflags --1st time
		m.z = RAIL.GetRailZ(rail, m, 0)
		if (rail.floorspriteslope) --Mimmick slope values if needed.
			P_CreateFloorSpriteSlope(m)
			m.floorspriteslope.xydirection = rail.floorspriteslope.xydirection
			m.floorspriteslope.zangle = rail.floorspriteslope.zangle
			m.floorspriteslope.o = {x = rail.floorspriteslope.o.x, y = rail.floorspriteslope.o.y, z = rail.floorspriteslope.o.z}
		end
		m.GSrailtophitbox = rail.GSrailbottomhitbox or 0
		m.GSrailbottomhitbox = rail.GSrailbottomhitbox or 0
		m.GScantgrind = true --Any mobjs with GScantgrind cannot grind. Naturally, rails can't grind themselves.
		
		m.GSspritewidth,m.spritexoffset = rail.GSspritewidth, rail.GSspritewidth
		m.GShangrail = rail.GShangrail 
		if (STRETCHHB!="STRETCH")
			m.color,m.colorized = rail.color,rail.colorized
			m.renderflags = rail.renderflags --2nd time
			if rail.GSinvisiblerail or rail.sprite == SPR_NULL --Become invisible as well!
				m.sprite = SPR_NULL
				m.GSinvisiblerail = true
				m.GSrailtophitbox = m.GSrailtophitbox and $+4*FRACUNIT or 4*FRACUNIT
			end
			if m.GSrailsideL and m.GSrailsideL.valid --The rail can only have 1 side, obviously!
				P_RemoveMobj(m.GSrailsideL)
			end
			
			m.GSrailsideL = RAIL.ConnectRails4(rail, m, -1) --Now put in the sides!
			if rail.GSrenderstyle == "notop" --Don't draw the splat? (Segment needs to continue existing for its hitbox, though!)
				rail.flags2 = $|MF2_DONTDRAW
			end
			local INFO = rail.GSinfo
			if INFO
				if INFO.renderflags
					m.renderflags = $|(INFO.renderflags)
				end
				if INFO.blendmode
					m.blendmode = INFO.blendmode
				end			
				if INFO.TOPsprite and not (rail.GSinvisiblerail)
					m.sprite = INFO.TOPsprite
					m.frame = INFO.TOPstartframe or A --Just to be sure.
				end
				if INFO.translucent
					m.frame = ($ & ~FF_TRANSMASK)|(INFO.translucent)
				end
			end
		end
		if (rail.GSloopflags & 2097152)
			m.flags = ($|MF_NOCLIPTHING|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOBLOCKMAP) & ~MF_SOLID
		end
	--	RAIL.print("Put Segment "+segnum+" down. Mainrailnum = "+rail.GSrailnum, 7)
		return m --
	end	
end

--V3 is a vertical variant of ConnectRails3.
RAIL.ConnectRailsV3 = function(rail, segnum, lastseg)	
	local RAILANG = RAIL.ANG(rail)
	
	--Spawn! One sprite is exactly 120 pixels, AKA 120 FRACUNITs in SRB2 dimensions.
	local m = P_SpawnMobjFromMobj(lastseg or rail, 0, 0, 0, MT_GSRAILSEG)
	if type(m)=="userdata" and userdataType(m)=="mobj_t" and m.valid
		m.state = S_GSRAIL
		m.GSmainrail = rail
		m.sprite = rail.sprite
		m.frame = rail.frame
		m.GSrailTrueAngle = rail.GSrailTrueAngle
		m.angle = RAILANG
		
		m.GSsegnum = segnum
		m.GSrailnum = rail.GSrailnum
		
		m.GSrailtophitbox = rail.GSrailbottomhitbox or 0
		m.GSrailbottomhitbox = rail.GSrailbottomhitbox or 0
		m.GScantgrind = true --Any mobjs with GScantgrind cannot grind. Naturally, rails can't grind themselves.
		
		m.GSspritewidth,m.spritexoffset = rail.GSspritewidth, rail.GSspritewidth --Is this a BIG issue??
			
		m.flags = ($|MF_SOLID|MF_SCENERY|MF_NOBLOCKMAP) & ~MF_NOCLIPTHING
		m.flags,m.flags2 = rail.flags, rail.flags2 --We can always set the sprite to SPR_NULL for invisible grindable rails.
		
		m.color,m.colorized = rail.color,rail.colorized
		m.renderflags = rail.renderflags
		m.GShangrail = rail.GShangrail 
		m.GSVertRail = rail.GSVertRail
		if rail.GSinvisiblerail or rail.sprite==SPR_NULL --Become invisible as well!
			m.sprite = SPR_NULL
			m.GSinvisiblerail = true
			m.GSrailtophitbox = m.GSrailtophitbox and $+4*FRACUNIT or 4*FRACUNIT
		end
		if rail.GSinfo
			local INFO = rail.GSinfo
			if INFO.renderflags
				m.renderflags = $|(INFO.renderflags)
			end
			if INFO.blendmode
				m.blendmode = INFO.blendmode
			end			
			if INFO.TOPsprite and not (rail.GSinvisiblerail)
				m.sprite = INFO.TOPsprite
				m.frame = INFO.TOPstartframe or A --Just to be sure.
			end
			if INFO.translucent
				m.frame = ($ & ~FF_TRANSMASK)|(INFO.translucent)
			end
		end
		if m.floorspriteslope
			P_RemoveFloorSpriteSlope(m) --Should never be necessary, but just incase.
		end
		if (rail.GSloopflags & 2097152) --Cannot touch!
			m.flags = ($|MF_NOCLIPTHING|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOBLOCKMAP) & ~MF_SOLID
		end
		if rail.GSnextrail and (rail.GSnextrail.z < rail.z)
			m.z = $-(120<<16)
		else
			m.z = $+(120<<16)
		end
		return m --
	end	
end

RAIL.ConnectRails4 = function(rail, segment, try)
	if not (segment and segment.valid and rail and rail.valid) --We're deep in, there's alot to check over now...
	or segment.GSrailsideL and segment.GSrailsideL.valid
	or not (rail.GSnextrail and rail.GSnextrail.valid)	
		RAIL.print("Failed to put railside down.", 11) return --
	elseif try==1
		return --Lets forget about double-sided rails for now.
	elseif rail.GSrenderstyle == "nosides" or rail.GSinvisiblerail or rail.sprite == SPR_NULL
		if segment.GSrailsideL and segment.GSrailsideL.valid --We only need one.
			P_RemoveMobj(segment.GSrailsideL)
		end			
		return --Don't create sides at all to save on resources if this flag is active.
	end
	
	local RAILANG = RAIL.ANG(rail)
	
--	Unused max quality version VVVV
--	local m = P_SpawnMobjFromMobj(segment,
--	FixedMul(segment.GSspritewidth/2, cos(RAILANG+(ANGLE_90*try))),
--	FixedMul(segment.GSspritewidth/2, sin(RAILANG+(ANGLE_90*try))), 0, MT_GSRAILSEG)	
	
	local m = P_SpawnMobjFromMobj(segment,0,0,0,MT_GSRAILSEG)
	if m and type(m)=="userdata" and userdataType(m)=="mobj_t" and m.valid
		if segment.GSrailsideL and segment.GSrailsideL.valid --We only need one.
			P_RemoveMobj(segment.GSrailsideL) --Only one at a time.
		end
		m.state = S_GSRAIL
		m.GSmainrail = rail
		m.GScantgrind = true
		m.GSsegment = segment
		m.GSrailnum = rail.GSrailnum
		if segment.floorspriteslope and segment.floorspriteslope.zangle --Mimmick slope values with rollangle?
			if (abs(segment.floorspriteslope.zangle) >= ANG2) --First lag, then memory leaks, then 5 degree rotation limitations? SRB2 rollangle is such trash.
				if (abs(segment.floorspriteslope.zangle) >= ANG1*64)
					P_RemoveMobj(m) return --nvm.
				else
					m.rollangle = segment.floorspriteslope.zangle
				end
			end
		end
		m.GSrailTrueAngle = RAILANG
		m.angle = segment.angle
		m.color,m.colorized = segment.color,segment.colorized
		m.frame = ((m.sprite==SPR_GSRL) and B or A)|FF_PAPERSPRITE
		m.renderflags = RF_PAPERSPRITE
		m.flags2 = segment.flags2 & ~MF2_SPLAT --We are NOT a splat. We're a papersprite.
		m.flags = (segment.flags & ~(MF_SOLID|MF_SPECIAL))|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOTHINK
		local INFO = rail.GSinfo
		if INFO
			if INFO.renderflags
				m.renderflags = $|(INFO.renderflags)
			end
			if INFO.blendmode
				m.blendmode = INFO.blendmode
			end			
			if INFO.SIDEsprite
				m.sprite = INFO.SIDEsprite
				m.frame = INFO.SIDEstartframe or A --Actually important.
			end
			if INFO.translucent
				m.frame = ($ & ~FF_TRANSMASK)|(INFO.translucent)
			end
		end
		return m --
	end
end

--Vertical version of Connect Rails 4, just like with V3.
RAIL.ConnectRailsV4 = function(rail, segment)
	if not (segment and segment.valid and rail and rail.valid)
	or rail.GSrenderstyle == "nosides" or (rail.GSinvisiblerail) or (rail.sprite == SPR_NULL)
	or not (rail.GSnextrail and rail.GSnextrail.valid)
		return --
	end
	if segment.GSrailsideL and segment.GSrailsideL.valid --We only need one.
		P_RemoveMobj(segment.GSrailsideL)
	end
	
	local RAILANG = RAIL.ANG(rail)
	local m = P_SpawnMobjFromMobj(segment, 0, 0, 0, MT_GSRAILSEG)
	if type(m)=="userdata" and userdataType(m)=="mobj_t" and m.valid
		m.GSrailTrueAngle = RAILANG
		m.angle = RAILANG
		m.dispoffset = segment.dispoffset
		m.color,m.colorized = segment.color,segment.colorized
		m.frame = ((m.sprite==SPR_GSRL) and B or A)|FF_PAPERSPRITE
		
		local ADD = -(6<<16)
		if rail.GSinfo
			local INFO = rail.GSinfo
			if INFO.renderflags
				m.renderflags = $|((INFO.renderflags)&~(FF_FLOORSPRITE|RF_SLOPESPLAT|RF_NOSPLATROLLANGLE|RF_NOSPLATBILLBOARD))
			end
			if INFO.blendmode
				m.blendmode = INFO.blendmode
			end			
			if INFO.SIDEsprite
				m.sprite = INFO.SIDEsprite
				m.frame = INFO.SIDEstartframe or A --Actually important.
			end
			if INFO.translucent
				m.frame = ($ & ~FF_TRANSMASK)|(INFO.translucent)
			end	
			if INFO.hangZoffset
				ADD = $+(INFO.hangZoffset<<16) --For sideways allignment.
			end		
		end
		if m.floorspriteslope
			P_RemoveFloorSpriteSlope(m) --Should never be necessary, but just incase.
		end
		m.flags2 = segment.flags2 & ~MF2_SPLAT --We are NO splat. We're a papersprite!
		m.flags = (segment.flags & ~(MF_SOLID|MF_SPECIAL))|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOBLOCKMAP|MF_NOTHINK
		m.renderflags = $|RF_PAPERSPRITE
		m.spritexscale,m.spriteyscale = segment.spritexscale, segment.spriteyscale
		m.spritexoffset,m.spriteyoffset = 0,0	
		
		local SETX = FixedMul(ADD, cos(RAILANG))
		local SETY = FixedMul(ADD, sin(RAILANG))
		local SETZ = 0
		if rail.GSnextrail and (rail.GSnextrail.z > rail.z)
			SETZ = $-(99<<16)
			m.frame = $|FF_VERTICALFLIP|FF_HORIZONTALFLIP --Face the right way, pls.
		else
			SETX,SETY = -$,-$
			SETZ = $-(199<<16)
			m.frame = ($ & ~FF_HORIZONTALFLIP)|FF_VERTICALFLIP 
		end
		m.rollangle = ANGLE_90	
		P_SetOrigin(m, m.x+SETX, m.y+SETY, segment.z+SETZ)
		return m --
	end	
end

RAIL.ScaleRailRollAngle = function(rail, seg, side)
	if not (side and side.valid) return end
	local t = rail if seg and seg.valid t = seg end --Should "t" be the rail itself or a rail segment? Prefer segment here.
	if side.rollangle
		local ZDELTA = side.rollangle
		if t.floorspriteslope and t.floorspriteslope.zangle
			ZDELTA = t.floorspriteslope.zangle
		end
		if (abs(ZDELTA) < ANG20) --Initial calculation for unsteep slopes. Highly preferred method.
			local ogdiff = (FixedDiv(FixedMul(rail.GSrailZDelta, t.spriteyscale), FRACUNIT)-rail.GSrailZDelta) or 1 
			side.spritexscale = FixedDiv(FixedMul(t.spriteyscale, FRACUNIT), FRACUNIT)+abs(ogdiff/6100) //(side.rollangle<0) and 6300 or 
			side.rollangle = $+ogdiff //(($ < 0) and FixedDiv(FixedMul(ogdiff, 17), 20) or ogdiff)	
		else --For steeper slopes, scale the whole rail. Comes with downsides, but at least it connects...
			side.scale = R_PointToDist2(0,0, tan(t.floorspriteslope and t.floorspriteslope.zangle or rail.GSrailZDelta or side.rollangle), t.spriteyscale*61/60)
			side.spritexscale,side.spriteyscale = 1<<16, 1<<16
			side.GSslopescale = side.scale
			rail.GSslopescale = side.scale
		end	
	else
		side.spritexscale = t.spriteyscale
		side.spriteyoffset = $-FixedDiv(FixedMul(side.spritexscale, 19), 10)
	end
	side.flags = ($|MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOTHINK) & ~(MF_SOLID|MF_SPECIAL)
	return side --
end

--Scan object "m", and either add it to a relevant table, or ignore it.
RAIL.DirectorScanObject = function(s, m, SEARCH) 
	if (m.type==MT_GSRAIL)
		RAIL.AddRail(s, m) --Add another rail to the table.
	elseif (m.type == MT_GSRAILLIGHT) or (m.type == MT_GSRAILDYE) or (m.type == MT_GSRAILPOLE) --Accessory objects!
		return m --	Add these after everything was set up.	
	elseif (m.flags & (MF_NOSECTOR|MF_AMBIENT|MF_NIGHTSITEM|MF_BOXICON))
	or (m.flags & (MF_MISSILE|MF_SPRING|MF_PAIN|MF_FIRE|MF_SCENERY)) and not (m.flags & (MF_ENEMY|MF_BOSS))		
		m.GScantgrind = true --Don't let guys with these properties grind!
	elseif not ((m.flags & (MF_ENEMY|MF_PUSHABLE|MF_SOLID|MF_BOSS)) or m.player) --Just so we don't have to do this later...
		m.GScantgrind = true --Don't let guys without these properties grind!
	else
		local spn = m.spawnpoint
		if (spn) and type(spn)=="userdata" and userdataType(spn)=="mapthing_t" and spn.valid
			if (udmf)~=true and (spn.options & MTF_EXTRA)
			or (udmf)==true and (spn.pitch == 7 or spn.roll == 7)  
				m.GScantgrind = true --You can manually prevent them from grinding too!
			end
		end
	end
end

--Scan for and attach everything found in Rail Director's extra stuff table. "s" must be the Rail Director.
RAIL.ScanAndAttach = function(s)
--	RAIL.print("\x8a Going through all "+s.GStotalrails+" rails...")
	local TABLE = s.GSextrastufftable
	if not (TABLE and #TABLE) return end --

	--Apply extra stuff mappers placed down!
	for _,m in pairs(TABLE)
		local m = TABLE[i]
		if not (m and type(m)=="userdata" and userdataType(m)=="mobj_t" and m.valid) continue end --
		
		local REMOVEIT
		if (m.type == MT_GSRAILDYE) --This thing dyes your entire rail into any color!				
			REMOVEIT = true
			local XYDIST = 20*max(FRACUNIT, m.scale)
			if m.GSraildye != nil and not (m.GSraildye > #skincolors-1)
				searchBlockmap("objects", function(ref, t)
					if t.valid and (t.type == MT_GSRAIL) and (FixedHypot(t.x-m.x, t.y-m.y) < XYDIST)
						local GS = m.GSgrind if GS==nil RAIL.SetupMobjRailVars(m) GS=m.GSgrind end
						t.GSraildye = m.GSraildye -- RAIL.print("Turn color "+m.GSraildye)
						GS.raildye = t.GSraildye
						t.color,t.colorized = m.GSraildye,m.colorized
						GS.myrail,m.GSmyrail = t,t return true --
					end
				end, m, m.x-XYDIST, m.x+XYDIST, m.y-XYDIST, m.y+XYDIST)	
			end
			if m.valid --Thanks! You can go home now!
				m.fuse = 3
				m.flags2 = $|MF2_DONTDRAW
			end
		elseif (m.type == MT_GSRAILLIGHT)
			if m.GSlineoflights
				local XYDIST = 50*max(FRACUNIT, m.scale)
  				searchBlockmap("objects", function(ref, t)
					if t.valid and (t.type == MT_GSRAIL) and (FixedHypot(t.x-m.x, t.y-m.y) < XYDIST)
						local GS = m.GSgrind if GS==nil RAIL.SetupMobjRailVars(m) GS=m.GSgrind end
						GS.myrail,m.GSmyrail = t,t return true --
					end
				end, m, m.x-XYDIST, m.x+XYDIST, m.y-XYDIST, m.y+XYDIST)							
			end
		elseif (m.type == MT_GSRAILPOLE)
			local XYDIST = 28*max(FRACUNIT, m.scale)
  			searchBlockmap("objects", function(ref, t)
  				if t.valid and (t.type == MT_GSRAIL) and (FixedHypot(t.x-m.x, t.y-m.y) < XYDIST)
					local GS = m.GSgrind if GS==nil RAIL.SetupMobjRailVars(m) GS=m.GSgrind end
					if m.GSraildye != nil and not (m.GSraildye > #skincolors-1)
						local GS = m.GSgrind if GS==nil RAIL.SetupMobjRailVars(m) GS=m.GSgrind end
						t.GSraildye = m.GSraildye --RAIL.print("Tur pole color:\x81 "+m.GSraildye)
						GS.raildye = t.GSraildye
						t.color,t.colorized = m.GSraildye,m.colorized
					end	
					GS.myrail,m.GSmyrail = t,t return true --
				end
			end, m, m.x-XYDIST, m.x+XYDIST, m.y-XYDIST, m.y+XYDIST)						
		end
		if REMOVEIT and m.valid
			if not (m.fuse and (m.fuse < 3))
				m.fuse = 3
			end
			m.flags2 = $|MF2_DONTDRAW
		--	table.remove(s.GSextrastufftable, m)
		end
	end
end

--Adds "m", a rail, to the Director Table. "s" must be the Rail Director.
RAIL.AddRail = function(s, m)
	if not (m and type(m)=="userdata" and userdataType(s)=="mobj_t" and not (m.GSrailsetup)) return end --
	s.GStotalrails = s.GStotalrails and $+1 or 1
	RAIL.ActivateRail(m)
	if m and m.valid --Still valid, I hope?
	/*	local NUM = m.GSrailnum if s.GSrailtable[NUM] NUM = nil end
		if NUM==nil 
			if s.GStempRtable==nil s.GStempRtable = {} end
			table.insert(s.GStempRtable, m) 
		else */
			table.insert(s.GSrailtable, m) //NUM, 
	--	end
	end
end

--Adds "m", an accessory, to the Director's "ExtraStuff" Table. "s" must be the Rail Director.
RAIL.AddAccessory = function(s, m)
	s.GStotalextras = s.GStotalextras and $+1 or 1
	table.insert(s.GSextrastufftable, m)
	m.GScantgrind = true
end

--We got a brand new grind rail director, let's have it assemble every rail in the map and get this show started.
RAIL.SetupRailDirector = function(s)
	if not (s and type(s)=="userdata" and userdataType(s)=="mobj_t" and s.valid) 
		return false --
	end
	if s.GStotalrails==nil
		s.GStotalrails = 0  --Amount of rails we'll be counting
		if s.GStotalextras==nil
			s.GStotalextras = 0 --Extra stuff like rail lights and such.
		end
	end
	if s.GSrailtable==nil
		s.GSrailtable = {}
		if s.GSextrastufftable == nil s.GSextrastufftable = {} end
	end
	
	local EXTRAS, EXNUM = {}, 0 --Accesory count.
	GS_RAILS_DIRECTOR = s --Do this twice in the hook.
	
	if s.GS_SetupRailDirector!=true
		--Start by adding all map-spawned rails to the Rail Director!
		--Also fill up the accesory object table, and add some properties to mobjs that shouldn't grind ever.
		for m in mobjs.iterate(mobjs)
			if type(m)=="userdata" and userdataType(m)=="mobj_t" and m.valid
				if (m.type==MT_GSRAILDIRECTOR) and (m!=s) and (m.GS_SetupRailDirector)
					s.fuse,s,GS_SetupRailDirector = 0,nil P_RemoveMobj(s) return --They're already here, we should be dead!
				end
				local ACC = RAIL.DirectorScanObject(s, m)
				if (ACC!=nil)
					EXNUM = $+1
					EXTRAS[EXNUM] = ACC
				end
			end
		end
	end
	local TOTALSEGS = 0
	local TOTALINVIS = 0
	local TOTALINVISSEGS = 0
	local TABLE = s.GSrailtable
	if TABLE and s.GStotalrails and (#TABLE) --Okay, everything's saved and set up. Now...connect the rails!
	
		local DOPITCH = {}
		local SAMENUM = 0
		
		--Connect the rails! First wave!
		for _,m in pairs(TABLE)
			if not (m and m.valid) continue end -- 
			RAIL.ConnectRails1(m, s)
			if not (m and m.valid) continue end -- 
			
			if m.GSnextrail==nil --Connect to pitch, or disappear?
				if m.GSConnectToPitch
					table.insert(DOPITCH, m)
				else
					m.flags2 = $|MF2_DONTDRAW
					m.shadowscale = 0
					if m.GSraildye
						m.color = m.GSraildye
					end
				end  
			elseif m.GSConnectToPitch and m.GSnextrail and m.GSnextrail.valid and (consoleplayer==server)
				RAIL.error("\x85"+"Warning:\x80 Rail number[\x82"+(m.GSrailnum or 0)+"\x80"+"] already connected to"+
				"\x80 Rail number[\x82"+(m.GSnextrail.GSrailnum or 0)+"\x80"+"]"+"and thus couldn't use ConnectToPitch["+(m.GSConnectToPitch)+
				"]"+". Ensure your rail doesn't connect to anything first!")
			end
		end
		TABLE = s.GSrailtable
		
		if (#DOPITCH) --Second connect! DOPITCH == true --Second wave.
			for _,m in pairs(DOPITCH)
				RAIL.ConnectRails1(m, s, m.GSConnectToPitch)
				if m and m.valid and not (m.GSnextrail and m.GSnextrail.valid)
					if consoleplayer and consoleplayer==displayplayer and consoleplayer.valid
						RAIL.MultiPrint(consoleplayer, "rail["+m.GSrailnum+"]"+" didn't find pitch"+"["+m.GSConnectToPitch+"]", "ERROR")
					end
					m.flags2 = $|MF2_DONTDRAW
					m.shadowscale = 0 if m.GSraildye m.color = m.GSraildye end
				end
			end
			TABLE = s.GSrailtable
		end 
		DOPITCH = nil --Thanks, we're done with you now.
		
		--Go through the table again now that everything is properly connected. Third wave!
		local NONUMBERS = 0
		local NUM = 0
		local MAP = mapheaderinfo[gamemap]
		
		for _,m in pairs(TABLE)
			if not (m and type(m)=="userdata" and m.valid) continue end --Not...valid...?
			
			TOTALSEGS = $+1
			m.GScheckwalljump = nil

			if m.GS_windrail or m.GS_elementrail --These are special cases added always.
				if not (m.GSanimatedrail)
					if s.GSanimator == nil s.GSanimator = {} end if s.DO==nil s.DO = P_RandomRange(0,4) end
					m.GSanimatedrail = (#(s.GSanimator))+1
					table.insert(s.GSanimator, m.GSanimatedrail, m)
				end
			end
			--Do we need to animate any rails? Put those in a special table.
			if not (m.GSnextrail and m.GSnextrail.valid)
				if not (m.GSprevrail and m.GSprevrail.valid)
					NONUMBERS = $+1
					RAIL.error("\x85"+"Warning:\x80 Rail number[\x82"+(m.GSrailnum or 0)+"\x80"+"] isn't connected to anything!"+
					" (Spawned\x81 debug TOAD\x80 on location)")
					RAIL.SpawnDebug(m)
					if m.GSrailsideL and m.GSrailsideL.valid P_RemoveMobj(m.GSrailsideL) end P_RemoveMobj(m)
				end continue --This is the end of a rail.
			end
			
			--So we DO have a next rail? In that case, set stuff up!
			NUM = $+1
			local NEXT = m.GSnextrail
			NEXT.colorized = m.colorized
			if m.GSraildye
				NEXT.GSraildye = m.GSraildye
				NEXT.color = m.color 
			end
			if m.GSrenderstyle
				NEXT.GSrenderstyle = m.GSrenderstyle
			end
			if m.GSrailtrans --For...binary, I think??? Might be defunct.
				m.frame = ($ & ~FF_TRANSMASK)|m.GSrailtrans
				NEXT.GSrailtrans = m.GSrailtrans
				NEXT.frame = ($ & ~FF_TRANSMASK)|m.GSrailtrans
			end
			
			--Should we animate any rails? Put those in a special table.
			if (m.top_endframe!=nil) and (m.top_endframe!=m.top_startframe)
			or (m.side_endframe!=nil) and (m.top_endframe!=m.side_startframe) 
				if not m.GSinvisiblerail and not (m.GSanimatedrail) --These were added seperately earlier!
				and (m.sprite!=SPR_NULL) and (m.sprite!=SPR_GSRL)
					if s.GSanimator == nil s.GSanimator = {} end if s.DO==nil s.DO=P_RandomRange(0,3) end
					m.GSanimatedrail = (#(s.GSanimator))+1
					table.insert(s.GSanimator, m.GSanimatedrail, m)
				end
			end
			if m.GSVertRail --Vertical rail.
				if m.floorspriteslope P_RemoveFloorSpriteSlope(m) end
				m.frame = $|FF_PAPERSPRITE|FF_VERTICALFLIP & ~FF_FLOORSPRITE
				m.renderflags = $|RF_PAPERSPRITE & ~(RF_SLOPESPLAT|RF_FLOORSPRITE|RF_NOSPLATBILLBOARD|RF_ABSOLUTEOFFSETS)
				m.flags2 = $ & ~MF2_SPLAT
				m.angle = $+ANGLE_90
				m.spritexoffset,m.spriteyoffset,m.dispoffset = 0,0,-444
				m.GSrailsideL = RAIL.ConnectRailsV4(m, m)
				if m.GSrailsideL and m.GSrailsideL.valid and (NEXT.z < m.z) and (m.GSrailsideL.z < m.z)
					m.z = m.GSrailsideL.z
				end
				for i = 1,2
					local NEXT = m.GSnextrail if i==2 NEXT = m.GSprevrail end
					if NEXT and NEXT.valid
						if i==1 and not (NEXT.GSnextrail and NEXT.GSnextrail.valid)
						or i==2 and not (NEXT.GSprevrail and NEXT.GSprevrail.valid)
							if NEXT.GSVertRail==nil
								NEXT.GSVertRail = m.GSVertRail
							end
							if NEXT.GSVertRailDOWNANG==nil
								NEXT.GSVertRailDOWNANG = m.GSVertRailDOWNANG
							end
							if NEXT.GSVertRailTopZ==nil
								NEXT.GSVertRailTopZ = m.GSVertRailTopZ
							end
							if NEXT.GSVertRailBotZ==nil						
								NEXT.GSVertRailBotZ = m.GSVertRailBotZ
							end
						end
					end
				end
			end

			local COLOR = RAIL.GrabRailColor(m) --Just to be sure, set color again.
			if (COLOR) or m.GSyMTscale 	--Main rail.
				if (COLOR) and (COLOR!=0) m.GSraildye,m.color = COLOR,COLOR end
				local SIDE = m.GSrailsideL
				if SIDE and SIDE.valid
					if (COLOR) SIDE.color,SIDE.GSraildye = COLOR,COLOR end
					if m.GSyMTscale
						SIDE.spriteyscale = FixedDiv(FixedMul($, m.GSyMTscale), FRACUNIT)
					end
				end
			end
			if m.GSxMTscale --Thinner/thicker rail top?
				m.spritexscale = FixedDiv(FixedMul($, m.GSxMTscale), FRACUNIT)
			end
			local LOOP = RAIL.LearnLoopType(m)
			if LOOP
				if LOOP.anim==0 and (m.GSloopflags & 131072) and not (m.GSinvisiblerail) and not (m.flags2 & MF2_DONTDRAW)
					m.GSupsidedown = true --Extremely specific use case.
				end
				if LOOP.walljump
					if not (m.GSloopflags & (262144|524288))
						m.radius = $+(15<<16)//min($, 35<<16) --More precise attachment.
					else
						m.radius = $+(42<<16) --Easier attachment, then!
					end
					m.GSwalljumprail = true
					if m.GSnextrail and m.GSnextrail.valid and not (m.GSnextrail.GSnextrail and m.GSnextrail.GSnextrail.valid)
						m.GSnextrail.GSwalljumprail = m.GSwalljumprail
					end
				end
			end
			if (m.GSloopflags & 2097152) --or ((m.GSconveyor or 0) > 0) and not (m.GSnextrail) or ((m.GSconveyor or 0) < 0) and not (m.GSprevrail)
				m.flags = ($|MF_NOCLIPTHING|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOBLOCKMAP|MF_SCENERY|MF_NOTHINK) & ~MF_SOLID
			end

			--Disable our rail right away?
			if m.GSdisableonwavethree
			or MAP and ( TRUEorFALSE(MAP.gsraildisableall)==true
			or MAP.gsraildisablerailtype!=nil and m.GStype!=nil and (m.GStype==tonumber(MAP.gsraildisablerailtype))	
			or MAP.gsraildisablerailtag!=nil and m.spawnpoint and (m.spawnpoint.tag==tonumber(MAP.gsraildisablerailtag))
			)
				m.GSdisabled = true
				m.flags = $|MF_NOCLIPTHING 
				local DDRAW = (not m.GSinvisiblerail) and MF2_DONTDRAW or 0 
				m.flags2 = $|DDRAW
				if m.GSrailsideL and m.GSrailsideL.valid
					m.GSrailsideL.flags2 = $|DDRAW
					m.GSrailsideL.flags = $|MF_NOCLIPTHING
				end
			end

			local SEGS = m.GSsegments
			if SEGS and (#SEGS)
				for _,seg in pairs(SEGS)
					if not (seg and seg.valid) continue end --
					TOTALSEGS = $+1
					if (seg.GSstretchhitbox) continue end --
					local SIDE = seg.GSrailsideL
					if (COLOR) seg.color,seg.GSraildye = COLOR,COLOR end
					if SIDE and SIDE.valid
						if (COLOR) SIDE.color,SIDE.GSraildye = COLOR,COLOR end
						if m.GSyMTscale
							SIDE.spriteyscale = FixedDiv(FixedMul($, m.GSyMTscale), FRACUNIT)
						end
						if m.GSslopescale and not SIDE.GSslopescale 
							SIDE.spriteyscale = m.GSslopescale
							SIDE.scale = m.GSslopescale
							if m.GSrailsideL and m.GSrailsideL.valid and not (m.GSrailsideL.GSslopescale)
								m.GSrailsideL.scale = SIDE.GSslopescale
								m.GSrailsideL.GSslopescale = SIDE.GSslopescale
								m.GSrailsideL.spriteyscale = SIDE.GSslopescale
							end
						end
					end
					if m.GSxMTscale
						seg.spritexscale = FixedDiv(FixedMul($, m.GSxMTscale), FRACUNIT)
					end
					if m.GSVertRail
						if seg.floorspriteslope P_RemoveFloorSpriteSlope(seg) end						
						seg.frame = $|FF_PAPERSPRITE|FF_VERTICALFLIP & ~FF_FLOORSPRITE
						seg.flags2 = $ & ~MF2_SPLAT
						seg.renderflags = $|RF_PAPERSPRITE & ~(RF_SLOPESPLAT|RF_FLOORSPRITE|RF_NOSPLATBILLBOARD|RF_ABSOLUTEOFFSETS)
						seg.angle = $+ANGLE_90 --One of the many reasons we have RAIL.ANG...
						seg.spritexoffset,seg.spriteyoffset,seg.dispoffset = 0,0,-444
						if m.GSwalljumprail
							seg.GSwalljumprail = m.GSwalljumprail
							seg.radius = min($, 32<<16)
						end
						seg.GSrailsideL = RAIL.ConnectRailsV4(m, seg)
					end
					if not (m.GSnextrail and m.GSnextrail.valid) or m.GSrailXYlength==nil or m.GSnextrail==m
						seg.flags2 = $|MF2_DONTDRAW
						seg.sprite = SPR_NULL
						if seg.GSrailsideL and seg.GSrailsideL.valid
							seg.GSrailsideL.flags2 = $|MF2_DONTDRAW
							P_RemoveMobj(seg.GSrailsideL)
						end	
						P_RemoveMobj(seg)
					else
						seg.flags2 = $|MF2_DONTRESPAWN
						seg.momx,seg.momy,seg.momz,seg.pmomz = 0,0,0,0
						seg.tracer,seg.target = nil
						if (m.GSloopflags & 2097152)						
							seg.flags = ($|MF_NOCLIPTHING|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOBLOCKMAP|MF_SCENERY|MF_NOTHINK) & ~MF_SOLID
						else --Ensure a couple of flags.
							seg.flags = $|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOGRAVITY
							if (seg.flags & MF_SOLID) and not (seg.flags & MF_NOCLIPTHING) and not (m.GSdisabled)
								seg.flags = $ & ~(MF_NOTHINK|MF_NOBLOCKMAP)
							end		
						end
						if m.GSdisabled==true --Disabled?
							seg.flags = $|MF_NOCLIPTHING
							if not m.GSinvisiblerail seg.flags2 = $|MF2_DONTDRAW end
							if seg.GSrailsideL and seg.GSrailsideL.valid
								if not m.GSinvisiblerail seg.GSrailsideL.flags2 = $|MF2_DONTDRAW end
								seg.GSrailsideL.flags = $|MF_NOCLIPTHING
							end					
						end				
					end
				end
			else
				m.GSsegments = nil --No need for a table if you've got no segments.
			end	
			if not (m.GSnextrail and m.GSnextrail.valid) or m.GSrailXYlength==nil or m.GSnextrail==m
				m.flags2 = $|MF2_DONTDRAW
				m.sprite = SPR_NULL
				if m.GSrailsideL and m.GSrailsideL.valid
					m.GSrailsideL.flags2 = $|MF2_DONTDRAW
					P_RemoveMobj(m.GSrailsideL)
				end
			elseif m.GSnextrail and m.GSnextrail.valid
				if m.GSrailallignobjects
					RAIL.AllignItemsWithRail(m) --Make sure this goes last. Attach suitable nearby objects to the rail if we were given this property!
				end
				if (m.GSgrindflags & 1048576) and not (m.GSloopflags & 2097152) --Spawn a homing attack point on the rail's middle.
					local ghs = P_SpawnMobj(m.x,m.y,m.z, MT_THOK)
					if ghs and ghs.valid
						local snapX = m.x-FixedMul(m.GSrailXYlength/2, cos(m.angle))
						local snapY = m.y-FixedMul(m.GSrailXYlength/2, sin(m.angle))	
						local snapZ = RAIL.GetRailZ(m, ghs, 0, nil, {X=snapX, Y=snapY})
						ghs.flags2 = $|MF2_DONTDRAW
						if not (m.flags & MF_NOCLIPTHING) and (m.GSdisabled!=true)
							ghs.flags2 = $|MF2_INVERTAIMABLE
						end
						ghs.GShomingtarget = m --We both keep track of each other!
						m.GShomingpoint = ghs
						ghs.tics = -1 if ((ghs.fuse or 0)>0) ghs.fuse = max($, ANGLE_135) end
						P_MoveOrigin(ghs, snapX, snapY, snapZ-(10<<16)) 
						ghs.flags = ($|MF_NOSECTOR|MF_NOTHINK|MF_NOCLIP|MF_NOGRAVITY) & ~MF_SOLID
						if (m.GSgrindflags & 524288) and not (m.GSconveyor)
							m.height = max($, 60<<16)
							ghs.z = $+(30<<16)
						elseif (abs(m.z-m.floorz) < 42<<16) 
							ghs.z = $+ (((m.GSgrindflags & 524288) and 14 or 2)<<16)
						end
						ghs.scale = ($*3)/2
					end
				end
			end
			m.health = 0
			m.flags = ($|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOGRAVITY) & ~(MF_SHOOTABLE|MF_BOSS|MF_ENEMY)
			m.flags2 = ($|MF2_DONTRESPAWN|MF2_FRET|MF2_BOSSDEAD) & ~(MF2_INVERTAIMABLE|MF2_OBJECTFLIP)
			m.eflags = $ & ~(MFE_SPRUNG|MFE_APPLYPMOMZ)
			m.target,m.tracer,s.GSMapThinged = nil
			m.GSroll,m.GSpitch,m.GSrailsetup,m.GSdisableonwavethree = nil
			m.momx,m.momy,m.momz,m.pmomz = 0,0,0,0
			if (m.flags & MF_SOLID) and not (m.flags & MF_NOCLIPTHING) and not (m.GSdisabled)
				m.flags = $ & ~(MF_NOTHINK|MF_NOBLOCKMAP)
			end
			if (m.flags2 & MF2_DONTDRAW) or m.sprite==SPR_NULL or m.GSinvisiblerail or m.GSdisabled==true
				TOTALINVIS = $+1
			end
		end
		if (EXNUM) --Now that this done, look to attach special freeslotted objects like rail lights and raildyes, and such.
			for i = 1,EXNUM
				local M = EXTRAS[i]
				if (M) and M.valid
					RAIL.AddAccessory(s, M) --Add accessories at this stage.
				end
			end
		end		
		if s.GStotalextras 
			RAIL.ScanAndAttach(s) --Now apply extra stuff mappers placed down!
		end	
		if NUM==0 and (NONUMBERS > 2)
			TOTALSEGS = "BeginnerTrap"
		end		
	end
	GS_RAILS_DIRECTOR = s --Just incase this didn't come through.
	if (TOTALSEGS) --Report back the amount of rails and rail segments. Should help mappers keep track of their performance load.
		if TOTALSEGS=="BeginnerTrap"
			if consoleplayer and consoleplayer==displayplayer and consoleplayer.valid
				RAIL.MultiPrint(consoleplayer, "Give rails numbers! 1 goes to 2, 2 goes to 3, etc!", "ERROR")
			end
		else
			local TEXT = "\x82"+"GS GRINDRAILS\x80 SETUP COMPLETE! Total rails:\x83"+s.GStotalrails+"\x80"
			if TOTALINVIS
				TEXT = $+"(Invisible:\x88"+TOTALINVIS+"\x80"+")"
			end
			if consoleplayer and consoleplayer==displayplayer and consoleplayer.valid
			and consoleplayer.GSRO and (consoleplayer.GSRO.devmode)
				CONS_Printf(consoleplayer, "\x82"+"GS GRINDRAILS\x80 SETUP COMPLETE! Total rails:\x83"+s.GStotalrails+
				"\x80 Total segments:\x8c"+TOTALSEGS)
			end
		end
	end
/*	if s.GSrailtable and not (#(s.GSrailtable)) --Nothing?
		s.GSrailtable = nil
		s.GStotalextras = nil
		s.GSextrastufftable = nil --We're done with this now, variables may die.
	end */
	return true --
end


--Put down a "Rail Director" on map load. It saves everything we want to remember, and every rail has a link to it.
--Every rail object will have rail.director.(rail being mo) If you want to find a rail, just ask ANY rail segment to do:
--for i,m in pairs(rail.director.GSrailtable)
RAIL.MapLoad = function(gamemap)
	RAIL.lastmap = gamemap
	RAIL.MapReady = max(RAIL.MapReady or 0, 1)
	
	if (gamestate!=GS_TITLESCREEN) and gamemap!=nil
		--Oh boy, a gimmick that can destroy everything. GREAT.
		if RAIL.ReloadMap==true
			RAIL.ReloadMap = nil
			if GS_RAILS_DIRECTOR and type(GS_RAILS_DIRECTOR)=="userdata"
			and userdataType(GS_RAILS_DIRECTOR)=="mobj_t" and GS_RAILS_DIRECTOR.valid 
			and (GS_RAILS_DIRECTOR.type==MT_GSRAILDIRECTOR)
				GS_RAILS_DIRECTOR.fuse = 0 GS_RAILS_DIRECTOR.GS_SetupRailDirector = nil
				P_RemoveMobj(GS_RAILS_DIRECTOR)
			end
			--Sometimes we get a leak. I don't like it.
			for m in mobjs.iterate(mobjs)
				if m and type(m)=="userdata" and userdataType(m)=="mobj_t" and m.valid
					if (m.type==GS_RAILS_DIRECTOR)
						P_RemoveMobj(m)
					elseif (m.type==MT_GSRAIL) and m.GSspritewidth==nil --Just incase!
						RAIL.RailMobjSpawn(m) RAIL.MapThingSpawn(m, m.spawnpoint)				
					end
				end
			end
	/*		for m in mobjs.iterate(mobjs)
				if m and type(m)=="userdata" and userdataType(m)=="mobj_t" and m.valid
					if (m.type==MT_GSRAIL)
						RAIL.RailMobjSpawn(m) RAIL.MapThingSpawn(m, m.spawnpoint) print("Reset this one")
					elseif (m.type==MT_GSRAILSEG)
						P_RemoveMobj(m) print("\x85"+"REMOVED:"+m)
					end
				end
			end	*/
			RAIL.MapReady = 1 RAIL.MapLoad(gamemap) return --
		end
	end
	if (RAIL.MapReady < 9)
		if GS_RAILS_DIRECTOR and type(GS_RAILS_DIRECTOR)=="userdata" and userdataType(GS_RAILS_DIRECTOR)=="mobj_t"
		and GS_RAILS_DIRECTOR.valid and (GS_RAILS_DIRECTOR.type==MT_GSRAILDIRECTOR) and (GS_RAILS_DIRECTOR.GS_SetupRailDirector)
			GS_RAILS_DIRECTOR.fuse = 0
			GS_RAILS_DIRECTOR.GS_SetupRailDirector = nil print("\x82 kieel")
			P_RemoveMobj(GS_RAILS_DIRECTOR)
		end
		GS_RAILS_DIRECTOR = {}
		RAIL.DirectorSpawnOrThink(GS_RAILS_DIRECTOR, "MapLoad")
		if type(GS_RAILS_DIRECTOR)=="userdata" and userdataType(GS_RAILS_DIRECTOR)=="mobj_t" and GS_RAILS_DIRECTOR.valid	
			RAIL.MapReady = 2
			RAIL.DirectorSpawnOrThink(GS_RAILS_DIRECTOR, "PostThink") --May as well get started frame 1, then.
		end
	end
end

--Technically spawns before the MapThingAngle.
RAIL.RailMobjSpawn = function(m)
	if m
		if not (m.GStype)
			m.GStype = 0 --Start with a type of 0.
			if not (m.color)
				m.color = SKINCOLOR_RED --Default color	
			end
		end
		if m.GSspritewidth==nil
			m.GSspritewidth = 10<<16
		end
		if m.GSrailTrueAngle==nil
			m.GSrailTrueAngle = m.angle or 0
		end
		if m.GStouchflags==nil m.GStouchflags = 0 end
		if m.GSjumpflags==nil m.GSjumpflags = 0 end
		if m.GSgrindflags==nil m.GSgrindflags = 0 end
		if m.GSloopflags==nil m.GSloopflags = 0 end	
		
		m.GScantgrind = true --Rails cannot grind themselves, shockingly
		m.scale,m.spritexscale,m.spriteyscale = FRACUNIT,FRACUNIT,FRACUNIT --Never play into this, or things get screw quick.
	end
end

--This becomes your rail number. Thankfully, it's not actually saved in angle variants, but 1 to like...31000 or something.
RAIL.MapThingSpawn = function(s, mapthing, SKIPTABLE)
	if not (mapthing and type(mapthing)=="userdata" and userdataType(mapthing)=="mapthing_t" and s) return end --
	s.GSMapThinged = true
	if udmf==true
		local DOARG3 = false
		if userdataType(mapthing.args)=="mapthing.args" or userdataType(mapthing.args)=="mapthing_t.args"
			DOARG3 = true
		else
			local spn = s.spawnpoint
			if type(spn)=="userdata" and userdataType(spn)=="mapthing_t" 
			and (userdataType(spn.args)=="mapthing_t.args" or userdataType(spn.args)=="mapthing.args")
				mapthing,DOARG3 = spn, true
			else
				s.GSMapThinged = "TryAgain" return --print("\x89"+"Mamamia ["+mapthing.angle+"]")
			end
		end
	/*	or udmf==true and (not mapthing.args or userdataType(mapthing.args)!="mapthing_t.args")
		if s and (SKIPTABLE!=true)
			if TEMPTABLE==nil TEMPTABLE = {} end
			local NUM = max(1, #TEMPTABLE+1)
			table.insert(TEMPTABLE, NUM, m) print("ADDING TEMP:\x85"+"["+NUM+"]")
		end 
		if s s.GSMapThinged = "TryAgain" print("Added TryAgain"+((mapthing) and mapthing.angle or 0)) end return */
		
		if DOARG3==true and mapthing.args[3]
			s.GSrailnum = mapthing.args[3]
			s.GSUDMFang = mapthing.angle*ANG1
		else
			s.GSrailnum = mapthing.angle
		end	
	else
		s.GSrailnum = mapthing.angle
	end
	s.flags2 = $|MF2_DONTDRAW --To be a little prettier, be invisible first.
--	RAIL.print("\x8a Rail segment "+s.GSrailnum+" loaded!")
end

--Destroy the grind rail!! Normally, the rail's turned invisible and untouchable so we may reload it later.
RAIL.RailBusted = function(s,i,so,dmg,dmgtype)
	local mainrail = s.GSmainrail or s
	mainrail.GSdisabled = true
	if mainrail.GShomingpoint and mainrail.GShomingpoint.valid mainrail.GShomingpoint.flags2 = $ & ~MF2_INVERTAIMABLE end	
	s.shadowscale,s.health = 0, 1
	s.flags = $|MF_NOCLIPTHING
	if (mainrail.flags2 & MF2_DONTDRAW) or not mainrail.GSraillength and not mainrail.GSinvisiblerail
		return true --
	end
	local RAILANG = RAIL.ANG(mainrail)
	local rng,debris = P_RandomRange(7,44)
	for d = 1,16 --Rail destruction debris
		debris = P_SpawnMobjFromMobj(s, 
		FixedMul(s.GSraillength/d, cos(RAILANG+ANGLE_180)),
		FixedMul(s.GSraillength/d, sin(RAILANG+ANGLE_180)), 0, MT_BOSSJUNK)
		if debris and debris.valid
			debris.renderflags = mainrail.renderflags
			debris.flags = $|MF_BOUNCE|MF_GRENADEBOUNCE|MF_NOCLIPTHING
			debris.sprite = SPR_BRKN
			debris.frame = I|((d%3==0) and FF_HORIZONTALFLIP or 0)
			if (mainrail.color)
				debris.color,debris.colorized = mainrail.color,true
			end
			debris.scale = $/max(1,rng/14)
			debris.rollangle = d*ANGLE_22h
			P_InstaThrust(debris, (ANGLE_45*d)-(d*ANGLE_22h), rng*FRACUNIT)
			P_SetObjectMomZ(debris, (10-rng/10)*FRACUNIT, false)
		end	
	end
	if mainrail==s and not S_SoundPlaying(s, sfx_bedeen)
	/*	for z in players.iterate if not (z and z.mo and z.mo.valid) continue end
			if ((FixedHypot(FixedHypot(mainrail.x-z.mo.x, mainrail.y-z.mo.y), mainrail.z-z.mo.z)) < 2200<<16)
				S_StartSound(nil, sfx_s3kb4, z) S_StartSound(nil, sfx_kc40, z) S_StartSound(nil, sfx_bedeen, z)
			end
		end	*/
		S_StartSound(s, sfx_s3kb4) S_StartSound(s, sfx_kc40) S_StartSound(s, sfx_bedeen)
	end
	s.flags2 = $|MF2_DONTDRAW return true --
end

--Sets and then returns the parameters according to ARG table given. ARG2 is for color swapping and is optional.
RAIL.EditParameters = function(ARG, ARG2)
	local TOGGLE, DESTZ, NEWTYPE, HANGTOGGLE, COLORSWAP, ABSOLUTE, DAMAGE = nil
	if ARG!=nil local ARGS = ARG
		if (ARG[0]) --Toggle whether a rail is on or not?
			if ARG[0]==1
				TOGGLE = "off"
			elseif ARG[0]==2
				TOGGLE = "on"
			elseif ARG[0]==3
				TOGGLE = "toggle" --Toggles rails Off if they're On, or On if it's Off.
			elseif ARG[0]==4
				TOGGLE = "destroy" --Destroy the rail!
			end
		end
		if (ARG[1]) --Toggle Hangrail or not?
			if ARG[1]==1
				HANGTOGGLE = "on"
			elseif ARG[1]==2
				HANGTOGGLE = "off"
			elseif ARG[1]==3
				HANGTOGGLE = "toggle" --Toggles hangrail properties Off if they're On, or On if it's Off.
			end
		end
		if (ARG[2]) and type(ARG[2])=="number" --Raise/Lower tagged rail by a certain amount?
			DESTZ = (ARG[2])<<16 
		end
		if (ARG[3]) and type(ARG[3])=="number" --Change tagged rail's Custom Type?
			NEWTYPE = ARG[3] 
		end
		if (ARG[4]) and type(ARG[4])=="number"  --Change the tagged rail's Damage Type? [NOT READY]
			if ARG[4]==1
				DAMAGE = "burning"
			elseif ARG[4]==2
				DAMAGE = "electric"
			elseif ARG[4]==3
				DAMAGE = "none"
			elseif ARG[4]==4
				DAMAGE = "toggleburning"
			elseif ARG[4]==5
				DAMAGE = "toggleelectric"
			end
		end	
		if (ARG[5]) --If this is set, Z position is set to an absolute value, and "0" becomes a possible value.
			ABSOLUTE = ARG[5]
			if DESTZ==nil DESTZ = 0 end
		end
	end
	if ARG2!=nil 
		local COLOR,NUM = ARG2, nil
		if type(COLOR)=="string" --Custom rail color. Convenience feature for UDMF that doesn't require dye objects.
			NUM = rawget(_G, COLOR) --First, try converting SKINCOLOR_ constants.
			if not (NUM) 
				NUM = R_GetColorByName(COLOR) --That didn't work. Try converting names?
				if not (NUM) NUM = R_GetSuperColorByName(COLOR) end --SUPER names??
			end
		end
		if not (NUM) NUM = tonumber(COLOR) end --Did they input a number for some reason?
		if (NUM) and type(NUM)=="number" and not (NUM > #skincolors)
			COLORSWAP = NUM
		end
	end return TOGGLE, DESTZ, NEWTYPE, HANGTOGGLE, COLORSWAP, ABSOLUTE, DAMAGE --Remember, EVERYTHING could return nil
end

--Actual part that edits the rail. Assumes m (the rail) is already confirmed valid.
RAIL.DoEdit = function(m, TOGGLE, DESTZ, NEWTYPE, HANGTOGGLE, COLORSWAP, ABSOLUTE, DAMAGE)
	local MESSAGE = ""
	if NEWTYPE and (NEWTYPE!=m.GStype) --First, custom rail swapping!
		RAIL.UpdateRailType(m, NEWTYPE) 
		MESSAGE = $+"NEW TYPE:\x82"+NEWTYPE+"\x80 "
	end
	if TOGGLE!=nil	
		if TOGGLE!="destroy"
			MESSAGE = $+"RAILTOGGLE:"+"\x82"
		end
		if TOGGLE=="off" or TOGGLE=="toggle" and not (m.GSdisabled) or TOGGLE=="destroy"
			if TOGGLE=="destroy" --Destroy the rail! (Cosmetic effect, it's still only just disabled, technically)	
				RAIL.RailBusted(m, nil, nil, 1, DMG_NUKE)
				MESSAGE = $+"\x8f"+"DESTROYRAIL"+"\x80 "
			else
				MESSAGE = $+"Off"+"\x80 "
			end		
			m.GSdisabled = true
			m.flags = $|MF_NOCLIPTHING
			if m.GShomingpoint and m.GShomingpoint.valid m.GShomingpoint.flags2 = $ & ~MF2_INVERTAIMABLE end	
			if not m.GSinvisiblerail m.flags2 = $|MF2_DONTDRAW end
		elseif TOGGLE=="on" or TOGGLE=="toggle" and (m.GSdisabled)
			MESSAGE = $+"On"+"\x80 "
			m.GSdisabled = false
			m.flags = $ & ~MF_NOCLIPTHING
			if m.GShomingpoint and m.GShomingpoint.valid m.GShomingpoint.flags2 = $|MF2_INVERTAIMABLE end
			if not m.GSinvisiblerail and (m.GSnextrail and m.GSnextrail.valid) m.flags2 = $ & ~MF2_DONTDRAW end	
		end
	end		
	if HANGTOGGLE!=nil
		if HANGTOGGLE=="on"
			m.GShangrail = true 
		elseif HANGTOGGLE=="off"
			m.GShangrail = false
		elseif HANGTOGGLE=="toggle"
			m.GShangrail = not m.GShangrail
		end
		MESSAGE = $+"HANGRAIL:\x82"+HANGTOGGLE+"\x80 "
	end
	if (DAMAGE!=nil)
		MESSAGE = $+"DAMAGE:\x86"+DAMAGE
		if DAMAGE == "burning"
			m.GSgrindflags = ($&~4194304)|2097152 --Now "fire". Also removes lightning.
		elseif DAMAGE == "electric"
			m.GSgrindflags = ($&~2097152)|4194304 --Now "lightning". Also removes fire.
		elseif DAMAGE == "none"
			m.GSgrindflags = $&~(2097152|4194304) --No more damage!
		elseif DAMAGE == "toggleburning" --Toggles fire on or off.
			if (m.GSgrindflags & 2097152)
				m.GSgrindflags = $ & ~2097152
				MESSAGE = $+"\x87"+"OFF"
			else
				m.GSgrindflags = $|2097152
				MESSAGE = $+"\x87"+"ON"
			end
		elseif DAMAGE == "toggleelectric" --Toggles electricity on or off.
			if (m.GSgrindflags & 4194304)
				m.GSgrindflags = $ & ~4194304
				MESSAGE = $+"\x82"+"OFF"
			else
				m.GSgrindflags = $|4194304
				MESSAGE = $+"\x82"+"ON"
			end
		end
		MESSAGE = $+"\x80 "
	end
	if COLORSWAP!=nil
		m.color = COLORSWAP
		MESSAGE = $+"COLORSWAP:\x88"+COLORSWAP+"\x80 "
	end	
	
	local SEGS = m.GSsegments 
	if SEGS and (#SEGS) --Make sure the rail's inbetween segments also adjust.
		for _,seg in pairs(SEGS)
			if seg and seg.valid
				if TOGGLE!=nil
					seg.GSdisabled = m.GSdisabled
					if m.GSdisabled
						seg.flags = $|MF_NOCLIPTHING
						if not m.GSinvisiblerail seg.flags2 = $|MF2_DONTDRAW end
					else
						seg.flags = $ & ~MF_NOCLIPTHING
						if not m.GSinvisiblerail seg.flags2 = $ & ~MF2_DONTDRAW end
					end
				end
				if HANGTOGGLE!=nil
					seg.GShangrail = m.GShangrail
				end
				if DESTZ!=nil
					seg.z = ((not ABSOLUTE) and $ or 0)+DESTZ
					if seg.floorspriteslope and seg.floorspriteslope.o
						local X,Y,Z = seg.floorspriteslope.o.x, seg.floorspriteslope.o.y, seg.floorspriteslope.o.z
						seg.floorspriteslope.o = {x = X, y = Y, z = ((not ABSOLUTE) and Z or 0)+DESTZ}
					end
				end
				if COLORSWAP!=nil
					seg.color = COLORSWAP
				end
				local SIDE = seg.GSrailsideL --Change the paper sprite now.
				if SIDE and SIDE.valid
					if TOGGLE!=nil and not m.GSinvisiblerail
						SIDE.flags2 = ($ & ~MF2_DONTDRAW)|(seg.GSdisabled and MF2_DONTDRAW or 0)
					end
					if DESTZ!=nil
						SIDE.z = ((not ABSOLUTE) and $ or 0)+DESTZ
					end
					if COLORSWAP!=nil
						SIDE.color = COLORSWAP
					end
				end	
			end
		end
	end		
	
	--Back to the main rail object...!
	if DESTZ!=nil
		m.z = ((not ABSOLUTE) and $ or 0)+DESTZ
		if m.floorspriteslope and m.floorspriteslope.o
			local X,Y,Z = m.floorspriteslope.o.x, m.floorspriteslope.o.y, m.floorspriteslope.o.z
			m.floorspriteslope.o = {x = X, y = Y, z = ((not ABSOLUTE) and Z or 0)+DESTZ}
		end
		MESSAGE = $+"DESTZ:\x8b"+(DESTZ/FRACUNIT)+"\x80"+((ABSOLUTE) and "[ABSOLUTE] " or " ")
	end
	local SIDE = m.GSrailsideL --The rail itself has a side too!
	if SIDE and SIDE.valid
		if TOGGLE!=nil and not m.GSinvisiblerail and (m.GSnextrail and m.GSnextrail.valid)
			SIDE.flags2 = ($ & ~MF2_DONTDRAW)|(m.GSdisabled and MF2_DONTDRAW or 0)
		end
		if DESTZ!=nil
			SIDE.z = ((not ABSOLUTE) and $ or 0)+DESTZ
		end
		if COLORSWAP!=nil
			SIDE.color = COLORSWAP
		end
	end
	if (MESSAGE!="")
		if not ((MESSAGE==RAIL.LastMessage) and RAIL.LastLineTime==leveltime)	
			if consoleplayer and consoleplayer==displayplayer and consoleplayer.valid
			and consoleplayer.GSRO and (consoleplayer.GSRO.devmode) and (MESSAGE!=RAIL.LastMessage)
				CONS_Printf(consoleplayer, MESSAGE)
			end
			RAIL.LastLineTime = leveltime
			RAIL.LastMessage = MESSAGE
		end
	end
end

local DIRECTOR = GS_RAILS_DIRECTOR

--New railtype? Looks alot like the initial setup on first glance, but it changes alot of the steps!
RAIL.UpdateRailType = function(m, NUM)
	if NUM==m.GStype return false end --It's the same type. Keep it as-is.	
	local TYPE = GS_RAILS_TYPES[NUM]
	if TYPE==nil
		RAIL.error("\x85"+"____________________________________________"+"\n"+"\n"+"ERROR! RAILTYPE\x80"+"["+NUM+"]"+"is invalid\n"+
		"If you've made a new railtype, ensure it exists ASAP. Otherwise, check if you've input a valid number.") return --
	end
	
	m.GSinfo,m.GStype = {}, NUM
	local INFO = m.GSinfo
	
	if not (m.GSraildye)
		if (TYPE.defaultcolor!=nil)
			m.color = TYPE.defaultcolor
		end
	else
		m.color = m.GSraildye
	end
	
	if TYPE.noTOP
		m.GSrenderstyle = "notop" --Only 1 can be active at a time.
	elseif TYPE.noSIDE 
		m.GSrenderstyle = "nosides"	
	end
	if TYPE.renderflags
		m.renderflags = $|(TYPE.renderflags)
		INFO.renderflags = TYPE.renderflags
	end
	if TYPE.blendmode
		m.blendmode = TYPE.blendmode
		INFO.blendmode = TYPE.blendmode
	end	
	m.GSspritewidth = 10<<16
	m.spritexoffset = m.GSspritewidth
	INFO.hangZoffset = TYPE.hangZoffset or 0
	if (TYPE.Xoffset) and type(TYPE.Xoffset)=="number"
		m.GSspritewidth = $+((TYPE.Xoffset)<<16)
		m.spritexoffset = m.GSspritewidth
		INFO.Xoffset = TYPE.Xoffset
	end
	
	m.top_startframe = TYPE.TOPstartframe or A
	m.top_endframe = nil
	m.side_startframe = TYPE.SIDEstartframe or A
	m.side_endframe = nil
			
	if type(TYPE.TOPsprite)=="number" and (TYPE.TOPsprite) --Change sprite?
		if not m.GSinvisiblerail
			m.sprite = TYPE.TOPsprite
			m.frame = TYPE.TOPstartframe or A
		end
		INFO.TOPsprite = TYPE.TOPsprite
		INFO.TOPstartframe = TYPE.TOPstartframe or A			
		INFO.animspeed = max(TYPE.animspeed or 0, 1)
		if type(TYPE.TOPendframe)=="number"
			m.top_startframe = TYPE.TOPstartframe or A 
			m.top_endframe = max(m.top_startframe,TYPE.TOPendframe)
		end
	end
	if type(TYPE.SIDEsprite)=="number"
		INFO.SIDEsprite = TYPE.SIDEsprite
		INFO.SIDEstartframe = TYPE.SIDEstartframe or A
		INFO.animspeed = max(TYPE.animspeed or 0, 1)
		
		if type(TYPE.SIDEendframe)=="number"
			m.side_startframe = TYPE.SIDEstartframe or A
			m.side_endframe = max(m.side_startframe, TYPE.SIDEendframe)
		end
	end
	if TYPE.alwayscolorize
		if TYPE.alwayscolorize==2 --If it's 2, ALWAYS colorize no matter what.
			m.colorized = true
		elseif (m.color) and (m.color!=TYPE.defaultcolor)
			m.colorized = true --If it's anything else, only colorize if they're NOT the default color.
		end
	else
		m.colorized = false
	end
	if TYPE.animspeed and type((TYPE.animspeed))=="number"
		INFO.animspeed = abs(TYPE.animspeed)
	end		
	if (TYPE.translucent) and type((TYPE.translucent))=="number"
		INFO.translucent = TYPE.translucent
		m.frame = ($ & ~FF_TRANSMASK)|(TYPE.translucent)
	end
	if (TYPE.windrail) 
		if type(TYPE.windrail)=="number"
			m.GS_windrail = TYPE.windrail
		else
			m.GS_windrail = SKINCOLOR_GREY
		end
	end
	if TYPE.spawnleaves
		if type(TYPE.spawnleaves)=="number"
			INFO.spawnleaves = TYPE.spawnleaves
		else
			INFO.spawnleaves = SKINCOLOR_FOREST
		end
	end
	
	local SIDE = m.GSrailsideL
	if SIDE and SIDE.valid
		SIDE.GSraildye,SIDE.color,SIDE.colorized = m.GSraildye,m.color,m.colorized
		if m.GSinfo and m.GSinfo.SIDEsprite
			SIDE.sprite = m.GSinfo.SIDEsprite
		else
			SIDE.sprite = m.sprite
		end
		SIDE.frame = m.side_startframe or A	
		if INFO.translucent
			SIDE.frame = $|INFO.translucent
		end
		SIDE.blendmode = m.blendmode
		SIDE.renderflags = $ & ~(RF_FULLBRIGHT|RF_SEMIBRIGHT|RF_FULLDARK|RF_NOCOLORMAPS)
		if INFO.renderflags
			SIDE.renderflags = $|(INFO.renderflags)
		end
		if m.GSrenderstyle=="nosides"
			SIDE.flags2 = $|MF2_DONTDRAW
		end
	end
	if m.GSsegments --Look into all rail segments now.
		for _,seg in pairs(m.GSsegments)
			if seg and seg.valid
				seg.spritexscale = m.spritexscale
				seg.spritexoffset = m.spritexoffset
				seg.sprite = m.sprite
				seg.frame = m.frame
				seg.renderflags = m.renderflags
				seg.blendmode = m.blendmode
				seg.GSraildye,seg.color,seg.colorized = m.GSraildye,m.color,m.colorized
				if m.GSrenderstyle=="notop" --Don't draw the splat? (Segment needs to continue existing for its hitbox, though!)
					seg.flags2 = $|MF2_DONTDRAW
				end
				local SEGSIDE = seg.GSrailsideL --Can safely replace
				if SEGSIDE and SEGSIDE.valid
					if m.GSrenderstyle == "nosides"
						SEGSIDE.flags2 = $|MF2_DONTDRAW
					end
					SEGSIDE.GSraildye,SEGSIDE.color,SEGSIDE.colorized = m.GSraildye,m.color,m.colorized
					if SIDE and SIDE.valid
						SEGSIDE.sprite = SIDE.sprite
						SEGSIDE.frame = m.side_startframe or SIDE.frame or A
						SEGSIDE.blendmode = SIDE.blendmode
						SEGSIDE.renderflags = SIDE.renderflags
					end
				end
			end	
		end
	end
	
	--Add or remove it from the rail director?
	if (m.top_endframe!=nil) and (m.top_endframe!=m.top_startframe)
	or (m.side_endframe!=nil) and (m.side_endframe!=m.side_startframe)
	or m.GS_windrail
		DIRECTOR = GS_RAILS_DIRECTOR
		if not (m.GSanimatedrail) and DIRECTOR and DIRECTOR.valid
			if DIRECTOR.GSanimator==nil DIRECTOR.GSanimator = {} end
			m.GSanimatedrail = (#(DIRECTOR.GSanimator))+1
			table.insert(DIRECTOR.GSanimator, m.GSanimatedrail, m)
		end
	elseif m.GSanimatedrail
		DIRECTOR = GS_RAILS_DIRECTOR
		if DIRECTOR and DIRECTOR.GSanimator
			for K,rail in pairs(DIRECTOR.GSanimator) --Find and kill.
				if rail==m table.remove(DIRECTOR.GSanimator, K) m.GSanimatedrail = nil break end --
			end
		end
	end
end

--Lua version of the linedef action's "rail edit" function, meant for advanced users!
--If TARGET is a number instead of a mobj, it'll look for the rail using that same GS.railnumber.
--ARG must be an array table, from 0 to 9 like in line.args. Check LUA_SHOW in SL_GSGrindRailTestMaps.wad for a demonstration.
--COLORSWAP is only needed if you want to swap the rail's color, otherwise make it nil.
--If ENTIRESEQUENCE is set to true, it'll edit ALL rails your given rail is connected to.
RAIL.EDIT = function(TARGET, ARG, COLORSWAP, ENTIRESEQUENCE)
	if not (type(GS_RAILS_DIRECTOR)=="userdata" and GS_RAILS_DIRECTOR.valid and GS_RAILS_DIRECTOR.GSrailtable)
		RAIL.error("\x85"+"____________________________________________"+"\n"+"\n"+"ERROR! Rail Director did not spawn yet!\x80"+"\n"+
		"If you tried using a\x82 'Level Load'\x80 trigger, set the\x82 executor delay\x80 to\x88 20\x80 on the\x89 Call Lua\x80 linedef!")
		return --
	end
	if ARG==nil 			RAIL.error("\x85"+"ARG is nil!") return end	--
	if (type(ARG)!="table")	RAIL.error("\x85"+"ARG must be an array table!") return end	--
	if TARGET==nil  		RAIL.error("\x85"+"TARGET is nil! Input either a rail number or a rail mobj.") return --
	elseif type(TARGET)=="number" --Try scanning for the railnumber?
		for _,m in pairs(GS_RAILS_DIRECTOR.GSrailtable)
			if m and m.valid and m.GSrailnum==TARGET and (m.type==MT_GSRAIL) --It's a rail with a matching rail number?
				TARGET = m,m break --
			end
		end
	end
	if not ((TARGET) and type(TARGET)=="userdata" and TARGET.valid and (TARGET.type==MT_GSRAIL)) return end --Couldn't find rail!
	local TOGGLE, DESTZ, NEWTYPE, HANGTOGGLE, COLORSWAP, ABSOLUTE, DAMAGE = RAIL.EditParameters(ARG, COLORSWAP or nil) --Set properties...!
	RAIL.DoEdit(TARGET, TOGGLE, DESTZ, NEWTYPE, HANGTOGGLE, COLORSWAP, ABSOLUTE, DAMAGE) return TARGET --Success!
end
	
--Make a custom Call Lua function that can edit the properties of all tagged rails!
--This is called using linedef action 443, when the function name's field says RAILEDIT
RAIL.LINE_RailEdit = function(line, s)
	if not (type(GS_RAILS_DIRECTOR)=="userdata" and GS_RAILS_DIRECTOR.valid and GS_RAILS_DIRECTOR.GSrailtable)
		RAIL.error("\x85"+"____________________________________________"+"\n"+"\n"+"ERROR! Rail Director did not spawn yet!\x80"+"\n"+
		"If you tried using a\x82 'Level Load'\x80 trigger, set the\x82 executor delay\x80 to\x88 20\x80 on the\x89 Call Lua\x80 linedef!")
		return --
	end
	local ARG, SARG = line.args, nil
	if type(line.stringargs)=="userdata" and Msa[userdataType(line.stringargs)]==2 and (line.stringargs[1])
		SARG = line.stringargs[1]
	end
	
	local TOGGLE, DESTZ, NEWTYPE, HANGTOGGLE, COLORSWAP, ABSOLUTE, DAMAGE = RAIL.EditParameters(ARG or nil, SARG or nil) --Apply properties.
	local TAGS = {}
	if (line.taglist) and type(line.taglist)=="userdata" and userdataType(line.taglist)=="taglist"
		local LIST = line.taglist
		for i = 1,#(line.taglist) --Go through the list to see what to edit.
			if (LIST[i]) and (LIST[i]!=0)
				TAGS[i] = LIST[i] --print("Registered Tag:\x84"+LIST[i])
			end
		end
	elseif type(line.tag)=="number" and (line.tag!=0)
		TAGS[line.tag] = line.tag --print("Registered Line-Tag:\x84"+LIST[i])		
	end
	if not (#TAGS) RAIL.error("\x85"+"Error!\x82 No tags were input on linedef Identification!", 25) return end --

	for _,m in pairs(GS_RAILS_DIRECTOR.GSrailtable)
		if not (m and m.valid and (m.type==MT_GSRAIL) and m.spawnpoint and m.spawnpoint.valid) continue end --It's a rail?
		local spn,TAGLIST = m.spawnpoint,m.spawnpoint.taglist
		
		if TAGLIST and userdataType(TAGLIST)=="taglist"
			local EDITME = false
			for i = 1,#TAGLIST
				if TAGLIST[i] and (TAGLIST[i]!=0) and (EDITME!=true)
					for _,RailTag in pairs(TAGS)
						if (RailTag) and TAGLIST[i]==RailTag and (EDITME!=true)
							EDITME = TAGLIST[i] break --
						end
					end 
					if (EDITME!=false) break end --
				end
			end
			if (EDITME==false) continue end --print("Editing rail with\x88 "+EDITME)
		else
			if not (line.tag and line.tag==spn.tag) continue end --print("Editing rail with\x81 "+line.tag)
		end
--		print("\x81"+"PRE:\x80"+"DESTZ:"+DESTZ/FRACUNIT+" ABSOLUTE:\x82"+ABSOLUTE)
		RAIL.DoEdit(m, TOGGLE, DESTZ, NEWTYPE, HANGTOGGLE, COLORSWAP, ABSOLUTE, DAMAGE)
	end return --
end
local function RAIL_LINE_EDIT(line, s) --Apparently doing this saves a joiner crash from ArchiveExtVars
	if not (line and line.valid) return end --Wha...no valid line?
	RAIL.LINE_RailEdit(line, s) --due to a gamestate becoming too big.
end
addHook("LinedefExecute", RAIL_LINE_EDIT, "RAILEDIT")

--When this mobj is destroyed, call the linedef executor that has their tag!
--By default applies to Jetty-Syns and Jetty-Gunners with the "Can Move" flag turned off.
--But feel free to apply this function to any enemy or object of your choosing!  
RAIL.Call_RailLineDef = function(s, i, so)
	if GS_RAILS==nil or not (s.spawnpoint and s.spawnpoint.valid) return end --
	local spn = s.spawnpoint
	local TAGS = {}
	if (spn.taglist) and type(spn.taglist)=="userdata"
		local LIST = spn.taglist
		for i = 0,#LIST
			if (LIST[i]) and LIST[i]!=0
				TAGS[i]=LIST[i]
			end
		end
	end
	local TAGS = {}
	if (spn.taglist) and type(spn.taglist)=="userdata" and userdataType(spn.taglist)=="taglist"
		local LIST = spn.taglist
		for i = 1,#LIST --Go through the list to see what to edit.
			if (LIST[i]) and (LIST[i]!=0)
				TAGS[i] = LIST[i]
			end
		end
	elseif type(spn.tag)=="number" and (spn.tag!=0)
		TAGS[1] = spn.tag 	
	end
	for _,LineTag in pairs(TAGS)
		if (LineTag) and (LineTag!=0)
			P_LinedefExecute(LineTag, nil, nil) --R_PointInSubsector(s.x, s.y).sector
		end
	end	
end

--Spawn the Director.
RAIL.Director_MobjSpawn = function(s)
	if s and s.valid
		s.GSTIME = leveltime or 0
		if not ((s.fuse or 0) > 0) s.fuse = 5 end
	end
end

--Checks if object is valid and not junk data.
RAIL.IsValid = function(s, TIME)
	if type(s)=="userdata" and userdataType(s)=="mobj_t" and s.valid
	--	if not TIME or (leveltime >= TIME)
			return true
	--	end
	end
end

--Checks if object is valid and not junk data, INCLUDING it's MapThing.
RAIL.IsMTValid = function(s, spn)
	if type(s)=="userdata" and type(spn)=="userdata" and userdataType(s)=="mobj_t"
	and userdataType(spn)=="mapthing_t" and s.valid --and spn.valid
		return true --
	end
end

--When a map changes, then...!
RAIL.MapChange = function(mapnum)	
	RAIL.INTTIMER,RAIL.LastErrorMessage,RAIL.LastMessage,RAIL.LastPrint,RAIL.LastLineTime,RAIL.ReloadMap = nil
	local MAP = mapnum
	if RAIL.lastmap==mapnum
		RAIL.MapReady = 0
		if type(mapnum)=="number" --Exception case.
			local MAP = mapheaderinfo[mapnum]
			if MAP and ((MAP.levelflags or 0) & LF_NORELOAD)
				RAIL.MapReady = 11
				RAIL.ReloadMap = true
				if (gamestate==GS_LEVEL) and GS_RAILS_DIRECTOR and GS_RAILS_DIRECTOR.valid
					P_RemoveMobj(GS_RAILS_DIRECTOR)
				end
			end
		end
	else
		RAIL.MapReady = 1
	end
	if (RAIL.MapReady!=11)
		GS_RAILS_DIRECTOR = {} --"reload"
		for z in players.iterate
			if z and z.valid 
				if z.solcam and z.solcam.GSdisabled
					z.solcam.emergencydisable,z.solcam.GSdisabled = false, false
				end		
				if z.GS_RAILDIRECTOR and RAIL.MapReady!=11
					z.GS_RAILDIRECTOR = 0
				end
			end
		end
	end
end

--Reset some variables and flags so the next SRB2 bootup isn't all messed up with settings.
RAIL.QuitGame = function(p)
	local p = consoleplayer
	if p and not (isdedicatedserver==true and consoleplayer==server) 
		if p.WasInRailMenu
			p.pflags = $ & ~PF_FORCESTRAFE		
		end
		if p.GSANflags!=nil
			p.pflags = ($ & ~(PF_ANALOGMODE|PF_DIRECTIONCHAR))|p.GSANflags
			p.GSANflags = nil
		end
	end
end
